FEM: unit tests, elmer sifio and geo writing
This commit is contained in:
@@ -269,6 +269,13 @@ SET(FemTests_SRCS
|
||||
test_files/ccx/Flow1D_thermomech.fcstd
|
||||
)
|
||||
|
||||
SET(FemTestsElmer_SRCS
|
||||
test_files/elmer/__init__.py
|
||||
test_files/elmer/case.sif
|
||||
test_files/elmer/group_mesh.geo
|
||||
test_files/elmer/ELMERSOLVER_STARTINFO
|
||||
)
|
||||
|
||||
SET(FemBase_SRCS
|
||||
FemMeshObject.cpp
|
||||
FemMeshObject.h
|
||||
@@ -379,6 +386,7 @@ fc_target_copy_resource(Fem
|
||||
${FemObjectsScripts_SRCS}
|
||||
${FemGuiScripts_SRCS}
|
||||
${FemTests_SRCS}
|
||||
${FemTestsElmer_SRCS}
|
||||
${FemSolver_SRCS}
|
||||
${FemElmer_SRCS}
|
||||
${FemCalculix_SRCS}
|
||||
|
||||
@@ -249,3 +249,14 @@ INSTALL(
|
||||
DESTINATION
|
||||
Mod/Fem/test_files/ccx
|
||||
)
|
||||
|
||||
INSTALL(
|
||||
FILES
|
||||
# changes on the file list here needs to be made in App/CMakeLists.txt as well
|
||||
test_files/elmer/__init__.py
|
||||
test_files/elmer/case.sif
|
||||
test_files/elmer/group_mesh.geo
|
||||
test_files/elmer/ELMERSOLVER_STARTINFO
|
||||
DESTINATION
|
||||
Mod/Fem/test_files/elmer
|
||||
)
|
||||
|
||||
@@ -42,6 +42,7 @@ temp_dir = tempfile.gettempdir() + '/FEM_unittests/'
|
||||
if not os.path.exists(temp_dir):
|
||||
os.makedirs(temp_dir)
|
||||
test_file_dir = home_path + 'Mod/Fem/test_files/ccx/'
|
||||
test_file_dir_elmer = home_path + 'Mod/Fem/test_files/elmer/'
|
||||
|
||||
# define some locations fot the analysis tests
|
||||
# since they are also used in the helper def which create results they should stay global for the module
|
||||
@@ -613,7 +614,7 @@ class FemCcxAnalysisTest(unittest.TestCase):
|
||||
fcc_print('Save FreeCAD file for frequency analysis to {}...'.format(frequency_save_fc_file))
|
||||
self.active_doc.saveAs(frequency_save_fc_file)
|
||||
|
||||
# use new solver frame work solver
|
||||
# use new solver frame work ccx solver
|
||||
fcc_print('Checking FEM new solver for new solver frame work...')
|
||||
solver_ccx2_object = ObjectsFem.makeSolverCalculix(self.active_doc, 'SolverCalculiX')
|
||||
solver_ccx2_object.GeometricalNonlinearity = 'linear'
|
||||
@@ -623,12 +624,14 @@ class FemCcxAnalysisTest(unittest.TestCase):
|
||||
solver_ccx2_object.EigenmodesCount = 10
|
||||
solver_ccx2_object.EigenmodeHighLimit = 1000000.0
|
||||
solver_ccx2_object.EigenmodeLowLimit = 0.0
|
||||
self.assertTrue(solver_ccx2_object, "FemTest of new solver failed")
|
||||
self.assertTrue(solver_ccx2_object, "FemTest of new ccx solver failed")
|
||||
analysis.Member = analysis.Member + [solver_ccx2_object]
|
||||
|
||||
fcc_print('Checking inpfile writing for new solver frame work...')
|
||||
if not os.path.exists(static2_analysis_dir):
|
||||
if not os.path.exists(static2_analysis_dir): # new solver frameworkd does explicit not create a non existing directory
|
||||
os.makedirs(static2_analysis_dir)
|
||||
|
||||
fcc_print('machine_ccx')
|
||||
machine = solver_ccx2_object.Proxy.createMachine(solver_ccx2_object, static2_analysis_dir)
|
||||
machine.target = femsolver.run.PREPARE
|
||||
machine.start()
|
||||
@@ -637,6 +640,46 @@ class FemCcxAnalysisTest(unittest.TestCase):
|
||||
ret = compare_inp_files(static_analysis_inp_file, static2_analysis_dir + mesh_name + '.inp')
|
||||
self.assertFalse(ret, "FemToolsCcx write_inp_file test failed.\n{}".format(ret))
|
||||
|
||||
# use new solver frame work elmer solver
|
||||
solver_elmer_object = ObjectsFem.makeSolverElmer(self.active_doc, 'SolverElmer')
|
||||
self.assertTrue(solver_elmer_object, "FemTest of elmer solver failed")
|
||||
analysis.Member = analysis.Member + [solver_elmer_object]
|
||||
solver_elmer_eqobj = ObjectsFem.makeEquationElasticity(self.active_doc, solver_elmer_object)
|
||||
self.assertTrue(solver_elmer_eqobj, "FemTest of elmer elasticity equation failed")
|
||||
|
||||
# set ThermalExpansionCoefficient, current elmer seams to need it even on simple elasticity analysis
|
||||
mat = material_object.Material
|
||||
mat['ThermalExpansionCoefficient'] = "0 um/m/K" # FIXME elmer elasticity needs the dictionary key, otherwise it fails
|
||||
material_object.Material = mat
|
||||
|
||||
mesh_gmsh = ObjectsFem.makeMeshGmsh(self.active_doc)
|
||||
mesh_gmsh.CharacteristicLengthMin = "9 mm"
|
||||
mesh_gmsh.FemMesh = mesh_object.FemMesh # elmer needs a GMHS mesh object, FIXME error message on Python solver run
|
||||
mesh_gmsh.Part = box
|
||||
analysis.Member = analysis.Member + [mesh_gmsh]
|
||||
self.active_doc.removeObject(mesh_object.Name)
|
||||
|
||||
fcc_print('machine_elmer')
|
||||
machine_elmer = solver_elmer_object.Proxy.createMachine(solver_elmer_object, static2_analysis_dir)
|
||||
machine_elmer.target = femsolver.run.PREPARE
|
||||
machine_elmer.start()
|
||||
machine_elmer.join() # wait for the machine to finish.
|
||||
|
||||
fcc_print('Test writing STARTINFO file')
|
||||
fcc_print('Comparing {} to {}'.format(test_file_dir_elmer + 'ELMERSOLVER_STARTINFO', static2_analysis_dir + 'ELMERSOLVER_STARTINFO'))
|
||||
ret = compare_files(test_file_dir_elmer + 'ELMERSOLVER_STARTINFO', static2_analysis_dir + 'ELMERSOLVER_STARTINFO')
|
||||
self.assertFalse(ret, "STARTINFO write file test failed.\n{}".format(ret))
|
||||
|
||||
fcc_print('Test writing case file')
|
||||
fcc_print('Comparing {} to {}'.format(test_file_dir_elmer + 'case.sif', static2_analysis_dir + 'case.sif'))
|
||||
ret = compare_files(test_file_dir_elmer + 'case.sif', static2_analysis_dir + 'case.sif')
|
||||
self.assertFalse(ret, "case write file test failed.\n{}".format(ret))
|
||||
|
||||
fcc_print('Test writing GMSH geo file')
|
||||
fcc_print('Comparing {} to {}'.format(test_file_dir_elmer + 'group_mesh.geo', static2_analysis_dir + 'group_mesh.geo'))
|
||||
ret = compare_files(test_file_dir_elmer + 'group_mesh.geo', static2_analysis_dir + 'group_mesh.geo')
|
||||
self.assertFalse(ret, "GMSH geo write file test failed.\n{}".format(ret))
|
||||
|
||||
fcc_print('Save FreeCAD file for static2 analysis to {}...'.format(static2_save_fc_file))
|
||||
self.active_doc.saveAs(static2_save_fc_file)
|
||||
|
||||
@@ -1065,6 +1108,28 @@ def compare_inp_files(file_name1, file_name2):
|
||||
return result
|
||||
|
||||
|
||||
def compare_files(file_name1, file_name2):
|
||||
file1 = open(file_name1, 'r')
|
||||
f1 = file1.readlines()
|
||||
file1.close()
|
||||
# workaraound for compare geos of elmer test and temporary file path (not only names change, path changes with operating system)
|
||||
lf1 = [l for l in f1 if not (l.startswith('Merge "') or l.startswith('Save "') or l.startswith('// '))]
|
||||
lf1 = force_unix_line_ends(lf1)
|
||||
file2 = open(file_name2, 'r')
|
||||
f2 = file2.readlines()
|
||||
file2.close()
|
||||
lf2 = [l for l in f2 if not (l.startswith('Merge "') or l.startswith('Save "') or l.startswith('// '))]
|
||||
lf2 = force_unix_line_ends(lf2)
|
||||
import difflib
|
||||
diff = difflib.unified_diff(lf1, lf2, n=0)
|
||||
result = ''
|
||||
for l in diff:
|
||||
result += l
|
||||
if result:
|
||||
result = "Comparing {} to {} failed!\n".format(file_name1, file_name2) + result
|
||||
return result
|
||||
|
||||
|
||||
def compare_stats(fea, stat_file=None, loc_stat_types=None, res_obj_name=None):
|
||||
if not loc_stat_types:
|
||||
loc_stat_types = stat_types
|
||||
|
||||
@@ -67,6 +67,7 @@ class Prepare(run.Prepare):
|
||||
|
||||
def run(self):
|
||||
self.pushStatus("Preparing input files...\n")
|
||||
# w = writer.Writer(self.solver, self.directory, True) # test mode
|
||||
w = writer.Writer(self.solver, self.directory)
|
||||
try:
|
||||
w.write()
|
||||
|
||||
@@ -91,10 +91,11 @@ def getConstant(name, dimension):
|
||||
|
||||
class Writer(object):
|
||||
|
||||
def __init__(self, solver, directory):
|
||||
def __init__(self, solver, directory, testmode=False):
|
||||
self.analysis = FemUtils.findAnalysisOfMember(solver)
|
||||
self.solver = solver
|
||||
self.directory = directory
|
||||
self.testmode = testmode
|
||||
self._usedVarNames = set()
|
||||
self._builder = sifio.Builder()
|
||||
self._handledObjects = set()
|
||||
@@ -122,15 +123,18 @@ class Writer(object):
|
||||
groups.extend(self._builder.getBodyNames())
|
||||
groups.extend(self._builder.getBoundaryNames())
|
||||
self._exportToUnv(groups, mesh, unvPath)
|
||||
binary = settings.getBinary("ElmerGrid")
|
||||
if binary is None:
|
||||
raise WriteError("Couldn't find ElmerGrid binary.")
|
||||
args = [binary,
|
||||
_ELMERGRID_IFORMAT,
|
||||
_ELMERGRID_OFORMAT,
|
||||
unvPath,
|
||||
"-out", self.directory]
|
||||
subprocess.call(args)
|
||||
if self.testmode:
|
||||
print("We are in testmode ElmerGrid may not be installed!")
|
||||
else:
|
||||
binary = settings.getBinary("ElmerGrid")
|
||||
if binary is None:
|
||||
raise WriteError("Couldn't find ElmerGrid binary.")
|
||||
args = [binary,
|
||||
_ELMERGRID_IFORMAT,
|
||||
_ELMERGRID_OFORMAT,
|
||||
unvPath,
|
||||
"-out", self.directory]
|
||||
subprocess.call(args)
|
||||
|
||||
def _writeStartinfo(self):
|
||||
path = os.path.join(self.directory, _STARTINFO_NAME)
|
||||
@@ -158,11 +162,16 @@ class Writer(object):
|
||||
tools.get_boundary_layer_data()
|
||||
tools.write_part_file()
|
||||
tools.write_geo()
|
||||
tools.run_gmsh_with_geo()
|
||||
if self.testmode:
|
||||
print("We are in testmode, GMSH may not be installed!")
|
||||
import shutil
|
||||
shutil.copyfile(geoPath, os.path.join(self.directory, "group_mesh.geo"))
|
||||
else:
|
||||
tools.run_gmsh_with_geo()
|
||||
|
||||
ioMesh = Fem.FemMesh()
|
||||
ioMesh.read(unvGmshPath)
|
||||
ioMesh.write(meshPath)
|
||||
ioMesh = Fem.FemMesh()
|
||||
ioMesh.read(unvGmshPath)
|
||||
ioMesh.write(meshPath)
|
||||
|
||||
os.remove(brepPath)
|
||||
os.remove(geoPath)
|
||||
|
||||
1
src/Mod/Fem/test_files/elmer/ELMERSOLVER_STARTINFO
Normal file
1
src/Mod/Fem/test_files/elmer/ELMERSOLVER_STARTINFO
Normal file
@@ -0,0 +1 @@
|
||||
case.sif
|
||||
29
src/Mod/Fem/test_files/elmer/__init__.py
Normal file
29
src/Mod/Fem/test_files/elmer/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 - Bernd Hahnebach <bernd@bimstatik.org> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "Fem ccx test cases"
|
||||
__author__ = "Bernd Hahnebach"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
|
||||
## @package ccx
|
||||
# \ingroup Fem
|
||||
# \brief Fem ccx test cases
|
||||
95
src/Mod/Fem/test_files/elmer/case.sif
Normal file
95
src/Mod/Fem/test_files/elmer/case.sif
Normal file
@@ -0,0 +1,95 @@
|
||||
Check Keywords Warn
|
||||
|
||||
Header
|
||||
Mesh DB "."
|
||||
End
|
||||
|
||||
Solver 1
|
||||
Bubbles = Logical False
|
||||
Calculate Pangle = Logical False
|
||||
Calculate Principal = Logical False
|
||||
Calculate Strains = Logical False
|
||||
Calculate Stresses = Logical False
|
||||
Displace mesh = Logical False
|
||||
Eigen Analysis = Logical False
|
||||
Eigen System Values = Integer 5
|
||||
Equation = String "Elasticity"
|
||||
Exec Solver = String "Always"
|
||||
Linear System Abort Not Converged = Logical False
|
||||
Linear System Convergence Tolerance = Real 1e-08
|
||||
Linear System Iterative Method = String "BiCGStab"
|
||||
Linear System Max Iterations = Integer 500
|
||||
Linear System Precondition Recompute = Integer 1
|
||||
Linear System Preconditioning = String "ILU0"
|
||||
Linear System Residual Output = Integer 1
|
||||
Linear System Solver = String "Iterative"
|
||||
Optimize Bandwidth = Logical True
|
||||
Procedure = File "StressSolve" "StressSolver"
|
||||
Stabilize = Logical True
|
||||
Steady State Convergence Tolerance = Real 1e-05
|
||||
Variable = String "Displacement"
|
||||
Variable DOFs = Integer 3
|
||||
End
|
||||
|
||||
Simulation
|
||||
BDF Order = Integer 1
|
||||
Coordinate Mapping(3) = Integer 1 2 3
|
||||
Coordinate System = String "Cartesian 3D"
|
||||
Output Intervals = Integer 1
|
||||
Simulation Type = String "Steady state"
|
||||
Steady State Max Iterations = Integer 1
|
||||
Steady State Min Iterations = Integer 0
|
||||
Timestepping Method = String "BDF"
|
||||
Use Mesh Names = Logical True
|
||||
End
|
||||
|
||||
Constants
|
||||
End
|
||||
|
||||
Body 1
|
||||
Equation = Integer 1
|
||||
Material = Integer 1
|
||||
Name = String "Solid1"
|
||||
End
|
||||
|
||||
Material 1
|
||||
Density = Real 7.9e-06
|
||||
Heat expansion Coefficient = Real 0.0
|
||||
Poisson ratio = Real 0.3
|
||||
Youngs Modulus = Real 200000000.0
|
||||
End
|
||||
|
||||
Equation 1
|
||||
Active Solvers(2) = Integer 1 2
|
||||
End
|
||||
|
||||
Solver 2
|
||||
Equation = String "ResultOutput"
|
||||
Exec Solver = String "After simulation"
|
||||
Output File Name = File "case"
|
||||
Procedure = File "ResultOutputSolve" "ResultOutputSolver"
|
||||
Vtu Format = Logical True
|
||||
End
|
||||
|
||||
Boundary Condition 1
|
||||
Name = String "Face2"
|
||||
Normal Force = Real -1000000.0
|
||||
End
|
||||
|
||||
Boundary Condition 2
|
||||
Displacement 1 = Real 0.0
|
||||
Displacement 2 = Real 0.0
|
||||
Displacement 3 = Real 0.0
|
||||
Name = String "Face1"
|
||||
End
|
||||
|
||||
Boundary Condition 3
|
||||
Force 1 = Real -0.0
|
||||
Force 1 Normalize by Area = Logical True
|
||||
Force 2 = Real -0.0
|
||||
Force 2 Normalize by Area = Logical True
|
||||
Force 3 = Real -40000000.0
|
||||
Force 3 Normalize by Area = Logical True
|
||||
Name = String "Face6"
|
||||
End
|
||||
|
||||
54
src/Mod/Fem/test_files/elmer/group_mesh.geo
Normal file
54
src/Mod/Fem/test_files/elmer/group_mesh.geo
Normal file
@@ -0,0 +1,54 @@
|
||||
// geo file for meshing with GMSH meshing software created by FreeCAD
|
||||
|
||||
// open brep geometry
|
||||
Merge "/tmp/tmp0TVZbM.brep";
|
||||
|
||||
// group data
|
||||
Physical Surface("Face1") = {1};
|
||||
Physical Surface("Face2") = {2};
|
||||
Physical Surface("Face6") = {6};
|
||||
Physical Volume("Solid1") = {1};
|
||||
|
||||
// Characteristic Length
|
||||
// no boundary layer settings for this mesh
|
||||
// min, max Characteristic Length
|
||||
Mesh.CharacteristicLengthMax = 1e+22;
|
||||
Mesh.CharacteristicLengthMin = 9.0;
|
||||
|
||||
// optimize the mesh
|
||||
Mesh.Optimize = 1;
|
||||
Mesh.OptimizeNetgen = 0;
|
||||
Mesh.HighOrderOptimize = 0; // for more HighOrderOptimize parameter check http://gmsh.info/doc/texinfo/gmsh.html
|
||||
|
||||
// mesh order
|
||||
Mesh.ElementOrder = 2;
|
||||
|
||||
// mesh algorithm, only a few algorithms are usable with 3D boundary layer generation
|
||||
// 2D mesh algorithm (1=MeshAdapt, 2=Automatic, 5=Delaunay, 6=Frontal, 7=BAMG, 8=DelQuad)
|
||||
Mesh.Algorithm = 2;
|
||||
// 3D mesh algorithm (1=Delaunay, 2=New Delaunay, 4=Frontal, 5=Frontal Delaunay, 6=Frontal Hex, 7=MMG3D, 9=R-tree)
|
||||
Mesh.Algorithm3D = 1;
|
||||
|
||||
// meshing
|
||||
Geometry.Tolerance = 1e-06; // set geometrical tolerance (also used for merging nodes)
|
||||
Mesh 3;
|
||||
Coherence Mesh; // Remove duplicate vertices
|
||||
|
||||
// save
|
||||
Mesh.Format = 2;
|
||||
// Ignore Physical definitions and save all elements;
|
||||
Mesh.SaveAll = 1;
|
||||
Save "/tmp/tmpjVhNNb.unv";
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// GMSH documentation:
|
||||
// http://gmsh.info/doc/texinfo/gmsh.html#Mesh
|
||||
//
|
||||
// We do not check if something went wrong, like negative jacobians etc. You can run GMSH manually yourself:
|
||||
//
|
||||
// to see full GMSH log, run in bash:
|
||||
// /usr/local/bin/gmsh - /tmp/tmputZ_uU.geo
|
||||
//
|
||||
// to run GMSH and keep file in GMSH GUI (with log), run in bash:
|
||||
// /usr/local/bin/gmsh /tmp/tmputZ_uU.geo
|
||||
Reference in New Issue
Block a user