patme 0.4.4__py3-none-any.whl → 0.4.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of patme might be problematic. Click here for more details.
- patme/femtools/fecall.py +118 -126
- patme/geometry/coordinatesystem.py +9 -0
- patme/mechanics/material.py +91 -14
- patme/service/mathutils.py +2 -1
- patme/sshtools/cara.py +9 -1
- patme/sshtools/clustercaller.py +42 -27
- patme/sshtools/sshcall.py +29 -17
- {patme-0.4.4.dist-info → patme-0.4.5.dist-info}/METADATA +6 -2
- {patme-0.4.4.dist-info → patme-0.4.5.dist-info}/RECORD +13 -13
- {patme-0.4.4.dist-info → patme-0.4.5.dist-info}/WHEEL +1 -1
- {patme-0.4.4.dist-info → patme-0.4.5.dist-info}/entry_points.txt +0 -0
- {patme-0.4.4.dist-info → patme-0.4.5.dist-info/licenses}/LICENSE +0 -0
- {patme-0.4.4.dist-info → patme-0.4.5.dist-info/licenses}/LICENSES/MIT.txt +0 -0
patme/femtools/fecall.py
CHANGED
|
@@ -70,6 +70,10 @@ class ResultLogFileChecker:
|
|
|
70
70
|
def checkResultLogFile(self, jobName):
|
|
71
71
|
"""returns true if no specified errors are found"""
|
|
72
72
|
errorFileName, errorPattern = self.getErrorFileAndErrorPattern(jobName)
|
|
73
|
+
if not os.path.exists(errorFileName):
|
|
74
|
+
log.info(f"{errorFileName} not found. Try {jobName}_err.log")
|
|
75
|
+
errorFileName = os.path.join(self.runDir, f"{jobName}_err.log")
|
|
76
|
+
|
|
73
77
|
if not os.path.exists(errorFileName):
|
|
74
78
|
log.error(f"Did not find given error file {errorFileName}")
|
|
75
79
|
return None
|
|
@@ -117,6 +121,7 @@ class FECaller(ResultLogFileChecker, ClusterCaller):
|
|
|
117
121
|
self.activateLicenseCheck = True
|
|
118
122
|
|
|
119
123
|
self._useNumberOfCores = 2
|
|
124
|
+
self._jobName = os.path.basename(os.path.splitext(self.feFilename)[0])
|
|
120
125
|
|
|
121
126
|
@staticmethod
|
|
122
127
|
def isLicenseAvailable():
|
|
@@ -134,7 +139,7 @@ class FECaller(ResultLogFileChecker, ClusterCaller):
|
|
|
134
139
|
:returns: True if there were no errors occured when calling the fe solver with the given input file
|
|
135
140
|
"""
|
|
136
141
|
if not jobName:
|
|
137
|
-
jobName =
|
|
142
|
+
jobName = self.jobName
|
|
138
143
|
|
|
139
144
|
if doRemoteCall:
|
|
140
145
|
try:
|
|
@@ -143,7 +148,7 @@ class FECaller(ResultLogFileChecker, ClusterCaller):
|
|
|
143
148
|
self.runRemote(copyFilesLocalToHost, jobName, **kwargs)
|
|
144
149
|
# check error file after remote files were copied to local machine
|
|
145
150
|
if not self.checkResultLogFile(jobName):
|
|
146
|
-
|
|
151
|
+
self.remoteCallFailed = True
|
|
147
152
|
else:
|
|
148
153
|
retVal = 8
|
|
149
154
|
|
|
@@ -184,25 +189,14 @@ class FECaller(ResultLogFileChecker, ClusterCaller):
|
|
|
184
189
|
msg += f"Acutal path: {toolexe}"
|
|
185
190
|
raise InternalError(msg)
|
|
186
191
|
|
|
187
|
-
toStderr =
|
|
188
|
-
|
|
189
|
-
toStderr = open(toStderr, "w+")
|
|
190
|
-
elif not toStderr:
|
|
191
|
-
toStderr = os.path.join(self.runDir, f"{jobName}_err.log")
|
|
192
|
-
toStderr = open(toStderr, "w+")
|
|
193
|
-
|
|
194
|
-
toStdout = kwargs.pop("toStdout", None)
|
|
195
|
-
if toStdout and isinstance(toStdout, str):
|
|
196
|
-
toStdout = open(toStdout, "w+")
|
|
197
|
-
elif not toStdout:
|
|
198
|
-
toStdout = os.path.join(self.runDir, f"{jobName}_out.log")
|
|
199
|
-
toStdout = open(toStdout, "w+")
|
|
192
|
+
toStderr = os.path.join(self.runDir, f"{jobName}_err.log")
|
|
193
|
+
toStdout = os.path.join(self.runDir, f"{jobName}_out.log")
|
|
200
194
|
|
|
201
195
|
retVal = subprocess.call(
|
|
202
196
|
self.localCmds,
|
|
203
197
|
cwd=self.runDir,
|
|
204
|
-
stderr=toStderr,
|
|
205
|
-
stdout=toStdout,
|
|
198
|
+
stderr=open(toStderr, "w+"),
|
|
199
|
+
stdout=open(toStdout, "w+"),
|
|
206
200
|
shell=self.localSubProcessSettings.get("shell", False),
|
|
207
201
|
)
|
|
208
202
|
|
|
@@ -243,7 +237,17 @@ class FECaller(ResultLogFileChecker, ClusterCaller):
|
|
|
243
237
|
def _setNumberOfCores(self, numCores):
|
|
244
238
|
self._useNumberOfCores = numCores
|
|
245
239
|
|
|
240
|
+
def _getJobName(self):
|
|
241
|
+
if self._jobName is None:
|
|
242
|
+
self._jobName = os.path.basename(os.path.splitext(self.feFilename)[0])
|
|
243
|
+
|
|
244
|
+
return self._jobName
|
|
245
|
+
|
|
246
|
+
def _setJobName(self, jobName):
|
|
247
|
+
self._jobName = jobName
|
|
248
|
+
|
|
246
249
|
useNumberOfCores = property(fget=_getNumberOfCores, fset=_setNumberOfCores)
|
|
250
|
+
jobName = property(fget=_getJobName, fset=_setJobName)
|
|
247
251
|
|
|
248
252
|
|
|
249
253
|
class AnsysCaller(FECaller):
|
|
@@ -334,7 +338,7 @@ class AnsysCaller(FECaller):
|
|
|
334
338
|
log.info("Check Ansys license availability.")
|
|
335
339
|
return 1 <= availableFlexlmLicenseTokens(licenseServerString, ansysLicense)
|
|
336
340
|
|
|
337
|
-
def generateSolverSpecificRemoteCmds(self):
|
|
341
|
+
def generateSolverSpecificRemoteCmds(self, jobName):
|
|
338
342
|
"""doc"""
|
|
339
343
|
baseFileInDir = os.path.basename(self.feFilename)
|
|
340
344
|
base, _ = os.path.splitext(baseFileInDir)
|
|
@@ -457,7 +461,7 @@ class NastranCaller(FECaller):
|
|
|
457
461
|
if platform.system() == "Linux":
|
|
458
462
|
self.localCmds.insert(5, "batch=no")
|
|
459
463
|
|
|
460
|
-
def generateSolverSpecificRemoteCmds(self):
|
|
464
|
+
def generateSolverSpecificRemoteCmds(self, jobName):
|
|
461
465
|
"""doc"""
|
|
462
466
|
baseFileInDir = os.path.basename(self.feFilename)
|
|
463
467
|
if self.clusterName == "cara":
|
|
@@ -503,10 +507,10 @@ class AbaqusCaller(FECaller):
|
|
|
503
507
|
"""This class realizes an interface to Abaqus"""
|
|
504
508
|
|
|
505
509
|
clusterAbaqusName = "abq2023"
|
|
510
|
+
solverName = "Abaqus"
|
|
506
511
|
|
|
507
512
|
def __init__(self, **kwargs):
|
|
508
513
|
FECaller.__init__(self, **kwargs)
|
|
509
|
-
self.solverName = "Abaqus"
|
|
510
514
|
# eclipse adds some env variabels that do not work with abaqus
|
|
511
515
|
os.environ.pop("PYTHONIOENCODING", None)
|
|
512
516
|
|
|
@@ -518,8 +522,8 @@ class AbaqusCaller(FECaller):
|
|
|
518
522
|
os.makedirs(tmpScratchFilesPath, exist_ok=True)
|
|
519
523
|
|
|
520
524
|
self.localCmds = [
|
|
521
|
-
abaqusPath,
|
|
522
|
-
"job
|
|
525
|
+
kwargs.pop("abaqusPath", abaqusPath),
|
|
526
|
+
f"job={self.jobName}",
|
|
523
527
|
"interactive",
|
|
524
528
|
"-scratch",
|
|
525
529
|
"abaqusScratch",
|
|
@@ -546,14 +550,14 @@ class AbaqusCaller(FECaller):
|
|
|
546
550
|
def getErrorFileAndErrorPattern(self, jobName):
|
|
547
551
|
"""doc"""
|
|
548
552
|
basePath = os.path.dirname(self.feFilename)
|
|
549
|
-
baseName = jobName if jobName else
|
|
553
|
+
baseName = jobName if jobName else self.jobName
|
|
550
554
|
useMsgFile = True
|
|
551
555
|
if useMsgFile:
|
|
552
556
|
filename = os.path.join(basePath, baseName + ".msg")
|
|
553
|
-
errorPattern = re.compile(r"\*{3}ERROR", RE_MULTILINE)
|
|
557
|
+
errorPattern = re.compile(r"\*{3}ERROR|Errno\s\d+", RE_MULTILINE)
|
|
554
558
|
else:
|
|
555
559
|
filename = os.path.join(basePath, baseName + ".dat")
|
|
556
|
-
errorPattern = re.compile(r"(error|Error)", RE_MULTILINE)
|
|
560
|
+
errorPattern = re.compile(r"(error|Error|Errno\s\d+)", RE_MULTILINE)
|
|
557
561
|
return filename, errorPattern
|
|
558
562
|
|
|
559
563
|
@staticmethod
|
|
@@ -578,14 +582,16 @@ class AbaqusCaller(FECaller):
|
|
|
578
582
|
log.debug(f"Number of possible abaqus runs: {numLic}")
|
|
579
583
|
return numLic
|
|
580
584
|
|
|
581
|
-
def generateSolverSpecificRemoteCmds(self):
|
|
585
|
+
def generateSolverSpecificRemoteCmds(self, jobName):
|
|
582
586
|
"""doc"""
|
|
583
587
|
baseFileInDir = os.path.basename(self.feFilename)
|
|
584
588
|
cmds = []
|
|
585
589
|
if "cara" in self.clusterName:
|
|
586
590
|
cmds += [
|
|
587
|
-
"module load env/
|
|
588
|
-
"module load
|
|
591
|
+
"module load env/spack",
|
|
592
|
+
"module load rev/23.05_r8",
|
|
593
|
+
"module load abaqus/2023",
|
|
594
|
+
f"touch {jobName}.msg",
|
|
589
595
|
]
|
|
590
596
|
cmds.append(
|
|
591
597
|
f"{self.clusterAbaqusName} job={baseFileInDir} interactive -scratch abaqusScratch -cpus {self.useNumberOfCores}"
|
|
@@ -606,7 +612,8 @@ class AbaqusPythonCaller(AbaqusCaller):
|
|
|
606
612
|
|
|
607
613
|
def __init__(self, **kwargs):
|
|
608
614
|
AbaqusCaller.__init__(self, **kwargs)
|
|
609
|
-
|
|
615
|
+
cae = kwargs.pop("cae", False)
|
|
616
|
+
self.solverName = "AbaqusPython" if not cae else "AbaqusCaePython"
|
|
610
617
|
# eclipse adds some env variabels that do not work with abaqus
|
|
611
618
|
os.environ.pop("PYTHONIOENCODING", None)
|
|
612
619
|
|
|
@@ -614,34 +621,40 @@ class AbaqusPythonCaller(AbaqusCaller):
|
|
|
614
621
|
if not self.pythonScriptPath:
|
|
615
622
|
raise ImproperParameterError("python script was not given!")
|
|
616
623
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
624
|
+
callParams = [abaqusPath]
|
|
625
|
+
if not cae:
|
|
626
|
+
callParams += ["python", self.pythonScriptPath]
|
|
627
|
+
else:
|
|
628
|
+
# instead of "noGUI" use "script" if cae should be opened in gui mode (it does not close automatically with "script")
|
|
629
|
+
callParams += ["cae", "noGUI=" + self.pythonScriptPath]
|
|
620
630
|
|
|
621
|
-
|
|
622
|
-
callParams += [">", f"{baseFile}.msg"]
|
|
631
|
+
callParams += kwargs.pop("arguments", [])
|
|
623
632
|
|
|
624
|
-
self.localCmds =
|
|
633
|
+
self.localCmds = callParams
|
|
625
634
|
|
|
626
|
-
def generateSolverSpecificRemoteCmds(self):
|
|
635
|
+
def generateSolverSpecificRemoteCmds(self, jobName):
|
|
627
636
|
"""doc"""
|
|
628
637
|
baseFile, ext = os.path.splitext(os.path.basename(self.feFilename))
|
|
629
638
|
|
|
630
|
-
callParms = self.localCmds[1:]
|
|
631
639
|
pyScriptBaseDir = os.path.dirname(self.pythonScriptPath)
|
|
632
640
|
pyScriptBaseFile = os.path.basename(self.pythonScriptPath)
|
|
633
641
|
|
|
634
642
|
if pyScriptBaseDir != self.runDir:
|
|
635
643
|
shutil.copy(self.pythonScriptPath, self.runDir)
|
|
636
644
|
|
|
637
|
-
callParms[1]
|
|
638
|
-
callParms[
|
|
645
|
+
callParms = self.localCmds[1:]
|
|
646
|
+
if "cae" in callParms[0]:
|
|
647
|
+
callParms[1] = "noGUI=" + pyScriptBaseFile
|
|
648
|
+
else:
|
|
649
|
+
callParms[1] = pyScriptBaseFile
|
|
639
650
|
|
|
640
651
|
runScriptCmds = []
|
|
641
652
|
if "cara" in self.clusterName:
|
|
642
653
|
runScriptCmds += [
|
|
643
|
-
"module load env/
|
|
644
|
-
"module load
|
|
654
|
+
"module load env/spack",
|
|
655
|
+
"module load rev/23.05_r8",
|
|
656
|
+
"module load abaqus/2023",
|
|
657
|
+
f"touch {jobName}.msg",
|
|
645
658
|
]
|
|
646
659
|
|
|
647
660
|
runScriptCmds.append(f"basefile={baseFile}")
|
|
@@ -661,7 +674,7 @@ class AbaqusPythonCaller(AbaqusCaller):
|
|
|
661
674
|
return runScriptCmds
|
|
662
675
|
|
|
663
676
|
|
|
664
|
-
class AbaqusPythonCaeCaller(
|
|
677
|
+
class AbaqusPythonCaeCaller(AbaqusPythonCaller):
|
|
665
678
|
"""This class realizes an interface to AbaqusCAE to call a Python script in an specified directory with the specified
|
|
666
679
|
input filename.
|
|
667
680
|
:param pythonSkriptPath: name of Abaqus Python input file optionally with relative or absolute path
|
|
@@ -671,19 +684,8 @@ class AbaqusPythonCaeCaller(AbaqusCaller):
|
|
|
671
684
|
"""
|
|
672
685
|
|
|
673
686
|
def __init__(self, **kwargs):
|
|
674
|
-
|
|
675
|
-
self
|
|
676
|
-
# eclipse adds some env variabels that do not work with abaqus
|
|
677
|
-
os.environ.pop("PYTHONIOENCODING", None)
|
|
678
|
-
|
|
679
|
-
pythonScriptPath = kwargs.pop("pythonSkriptPath", None)
|
|
680
|
-
if not pythonScriptPath:
|
|
681
|
-
raise ImproperParameterError("python script was not given!")
|
|
682
|
-
|
|
683
|
-
pyArguments = kwargs.pop("arguments", [])
|
|
684
|
-
# instead of "noGUI" use "script" if cae should be opened in gui mode (it does not close automatically with "script")
|
|
685
|
-
|
|
686
|
-
self.localCmds = [abaqusPath, "cae", "noGUI=" + pythonScriptPath] + pyArguments
|
|
687
|
+
kwargs["cae"] = True
|
|
688
|
+
AbaqusPythonCaller.__init__(self, **kwargs)
|
|
687
689
|
|
|
688
690
|
|
|
689
691
|
class B2000ppCaller(FECaller):
|
|
@@ -695,6 +697,8 @@ class B2000ppCaller(FECaller):
|
|
|
695
697
|
FECaller.__init__(self, **kwargs)
|
|
696
698
|
|
|
697
699
|
b2000Run = shutil.which("b2000++")
|
|
700
|
+
if not b2000Run:
|
|
701
|
+
b2000Run = "b2000++"
|
|
698
702
|
|
|
699
703
|
self.localCmds = [b2000Run, self.feFilename]
|
|
700
704
|
|
|
@@ -727,7 +731,7 @@ class B2000ppCaller(FECaller):
|
|
|
727
731
|
|
|
728
732
|
return FECaller.runLocal(self, jobName, **kwargs)
|
|
729
733
|
|
|
730
|
-
def generateSolverSpecificRemoteCmds(self):
|
|
734
|
+
def generateSolverSpecificRemoteCmds(self, jobName):
|
|
731
735
|
"""doc"""
|
|
732
736
|
|
|
733
737
|
baseFileInDir = os.path.basename(self.feFilename)
|
|
@@ -746,7 +750,11 @@ class B2000ppCaller(FECaller):
|
|
|
746
750
|
if ext == ".bdf":
|
|
747
751
|
runScriptCmds.append(f"b2convert_from_nas {baseFileInDir} {baseFile}.mdl")
|
|
748
752
|
|
|
749
|
-
runScriptCmds
|
|
753
|
+
runScriptCmds += [
|
|
754
|
+
f"b2ip++ {baseFile}.mdl",
|
|
755
|
+
f"b2000++ {baseFile}.b2m",
|
|
756
|
+
f"mv {baseFile}.b2m/log.txt {jobName}.log",
|
|
757
|
+
]
|
|
750
758
|
|
|
751
759
|
if "b2mconv.py" in os.listdir(self.runDir):
|
|
752
760
|
|
|
@@ -756,27 +764,29 @@ class B2000ppCaller(FECaller):
|
|
|
756
764
|
|
|
757
765
|
def getErrorFileAndErrorPattern(self, jobName):
|
|
758
766
|
"""doc"""
|
|
759
|
-
|
|
760
|
-
errorFile = os.path.join(resDir, "log.txt")
|
|
767
|
+
errorFile = os.path.join(self.runDir, f"{jobName}.log")
|
|
761
768
|
if not os.path.exists(errorFile):
|
|
762
769
|
errorFile = ""
|
|
763
770
|
|
|
764
|
-
return errorFile, re.compile("CRITICAL", RE_MULTILINE)
|
|
771
|
+
return errorFile, re.compile("CRITICAL|error|ERROR|Invalid", RE_MULTILINE)
|
|
765
772
|
|
|
766
773
|
|
|
767
774
|
class B2000ModelToDatabase(B2000ppCaller):
|
|
775
|
+
|
|
776
|
+
solverName = "b2ip++"
|
|
777
|
+
|
|
768
778
|
def __init__(self, **kwargs):
|
|
769
779
|
|
|
770
780
|
B2000ppCaller.__init__(self, **kwargs)
|
|
771
|
-
self.solverName = "b2ip++"
|
|
772
781
|
|
|
773
782
|
b2ipTool = shutil.which("b2ip++")
|
|
783
|
+
if not b2ipTool:
|
|
784
|
+
b2ipTool = "b2ip++"
|
|
774
785
|
|
|
775
786
|
basename = os.path.basename(self.feFilename)
|
|
776
|
-
baseDir = os.path.dirname(self.feFilename)
|
|
777
787
|
base, _ = os.path.splitext(basename)
|
|
778
788
|
|
|
779
|
-
workingDir = kwargs.pop("feWorkDir",
|
|
789
|
+
workingDir = kwargs.pop("feWorkDir", os.path.dirname(self.feFilename))
|
|
780
790
|
dbFile = os.path.join(workingDir, f"{base}.b2m")
|
|
781
791
|
|
|
782
792
|
self.localCmds = [b2ipTool, self.feFilename, dbFile]
|
|
@@ -785,17 +795,18 @@ class B2000ModelToDatabase(B2000ppCaller):
|
|
|
785
795
|
"""doc"""
|
|
786
796
|
return FECaller.runLocal(self, jobName, **kwargs)
|
|
787
797
|
|
|
788
|
-
def checkResultLogFile(self, jobName):
|
|
789
|
-
return True
|
|
790
|
-
|
|
791
798
|
|
|
792
799
|
class B2000FromBDFConverter(B2000ppCaller):
|
|
800
|
+
|
|
801
|
+
solverName = "b2convert_from_nas"
|
|
802
|
+
|
|
793
803
|
def __init__(self, **kwargs):
|
|
794
804
|
|
|
795
805
|
B2000ppCaller.__init__(self, **kwargs)
|
|
796
|
-
self.solverName = "b2convert_from_nas"
|
|
797
806
|
|
|
798
807
|
b2convTool = shutil.which("b2convert_from_nas")
|
|
808
|
+
if not b2convTool:
|
|
809
|
+
b2convTool = "b2convert_from_nas"
|
|
799
810
|
|
|
800
811
|
toMdlFile = kwargs.pop("toMdlFile", None)
|
|
801
812
|
if toMdlFile is None:
|
|
@@ -804,7 +815,7 @@ class B2000FromBDFConverter(B2000ppCaller):
|
|
|
804
815
|
|
|
805
816
|
self.localCmds = [b2convTool, self.feFilename, toMdlFile]
|
|
806
817
|
|
|
807
|
-
def generateSolverSpecificRemoteCmds(self):
|
|
818
|
+
def generateSolverSpecificRemoteCmds(self, jobName):
|
|
808
819
|
"""doc"""
|
|
809
820
|
baseFileInDir = os.path.basename(self.feFilename)
|
|
810
821
|
baseFile, _ = os.path.splitext(baseFileInDir)
|
|
@@ -818,97 +829,79 @@ class B2000FromBDFConverter(B2000ppCaller):
|
|
|
818
829
|
]
|
|
819
830
|
return runScriptCmds
|
|
820
831
|
|
|
821
|
-
def checkResultLogFile(self, jobName):
|
|
822
|
-
return True
|
|
823
832
|
|
|
833
|
+
class OpenRadiossCaller(FECaller):
|
|
824
834
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
REMOTE_SCRIPT = "run_b2mToPickle.sh"
|
|
828
|
-
solverName = "b2mToPickle"
|
|
835
|
+
solverName = "OpenRadioss"
|
|
829
836
|
|
|
830
837
|
def __init__(self, **kwargs):
|
|
831
|
-
|
|
832
|
-
B2000ppCaller.__init__(self, **kwargs)
|
|
833
|
-
|
|
834
|
-
self.pythonScriptPath = kwargs.pop("pythonScript", None)
|
|
835
|
-
if not self.pythonScriptPath:
|
|
836
|
-
raise Exception("python script was not given!")
|
|
837
|
-
|
|
838
|
-
pyArguments = kwargs.pop("arguments", [])
|
|
839
|
-
|
|
840
|
-
callParams = ["python", self.pythonScriptPath] + pyArguments
|
|
841
|
-
|
|
842
|
-
baseFile, _ = os.path.splitext(self.feFilename)
|
|
843
|
-
callParams += [">", f"{baseFile}.msg"]
|
|
844
|
-
|
|
845
|
-
self.localCmds = callParams
|
|
846
|
-
|
|
847
|
-
def generateSolverSpecificRemoteCmds(self):
|
|
848
838
|
"""doc"""
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
pyScriptBaseFile = os.path.basename(self.pythonScriptPath)
|
|
854
|
-
|
|
855
|
-
if pyScriptBaseDir != self.runDir:
|
|
856
|
-
shutil.copy(self.pythonScriptPath, self.runDir)
|
|
839
|
+
FECaller.__init__(self, **kwargs)
|
|
840
|
+
self.__engineFile = self.feFilename.replace("0000.rad", "0001.rad")
|
|
841
|
+
if not os.path.exists(self.__engineFile):
|
|
842
|
+
raise FileNotFoundError(f"Engine file {self.__engineFile} does not exist")
|
|
857
843
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
844
|
+
self.localCmds = []
|
|
845
|
+
self.remoteCmds = []
|
|
846
|
+
self.numThreadsOMP = kwargs.pop("numThreadsOMP", 2)
|
|
861
847
|
|
|
848
|
+
def generateSolverSpecificRemoteCmds(self, jobName):
|
|
849
|
+
"""doc"""
|
|
862
850
|
runScriptCmds = []
|
|
863
851
|
if "cara" in self.clusterName:
|
|
864
|
-
|
|
852
|
+
|
|
853
|
+
runScriptCmds += [
|
|
865
854
|
"module load env/spack",
|
|
866
855
|
"module load rev/23.05_r8",
|
|
867
856
|
"module use /sw/DLR/FA/BS/STM/modulefiles",
|
|
868
|
-
"module load
|
|
869
|
-
"
|
|
857
|
+
f"module load OpenRadioss",
|
|
858
|
+
f"export OMP_NUM_THREADS={self.numThreadsOMP}",
|
|
859
|
+
"export OMP_STACKSIZE=400m",
|
|
860
|
+
f"starter_linux64_gf -i {self.feFilename} -np 1 -nt {self.numThreadsOMP}",
|
|
861
|
+
f"engine_linux64_gf_ompi -i {self.__engineFile} -nt {self.numThreadsOMP}",
|
|
862
|
+
'find . -name "*A[0-9]*" -print0 | xargs -i -0 bash -c "anim_to_vtk_linux64_gf {} > {}.vtk"',
|
|
870
863
|
]
|
|
871
864
|
|
|
872
|
-
|
|
873
|
-
|
|
865
|
+
else:
|
|
866
|
+
raise Exception("Unknown cluster name")
|
|
874
867
|
|
|
875
|
-
|
|
876
|
-
return True
|
|
868
|
+
return runScriptCmds
|
|
877
869
|
|
|
878
870
|
|
|
879
871
|
class AsterCaller(FECaller):
|
|
872
|
+
|
|
873
|
+
solverName = "Code-Aster"
|
|
874
|
+
|
|
880
875
|
def __init__(self, **kwargs):
|
|
876
|
+
|
|
881
877
|
FECaller.__init__(self, **kwargs)
|
|
882
|
-
self.solverName = "Code-Aster"
|
|
883
878
|
|
|
884
879
|
asterBat = kwargs.pop("as_run_script", shutil.which("as_run"))
|
|
885
880
|
if not asterBat:
|
|
886
881
|
raise Exception("Cannot find as_run script to run a code aster study")
|
|
887
882
|
|
|
888
|
-
self.localCmds = [
|
|
883
|
+
self.localCmds = [asterBat, "--run", self.feFilename]
|
|
889
884
|
self.remoteCmds = []
|
|
890
885
|
|
|
891
886
|
def runLocal(self, jobName, **kwargs):
|
|
892
887
|
"""doc"""
|
|
893
888
|
self.localCmds = [jobName if elem == "<jobName>" else elem for elem in self.localCmds]
|
|
894
889
|
|
|
895
|
-
|
|
896
|
-
cmd = self.localCmds[0]
|
|
897
|
-
else:
|
|
898
|
-
cmd = ";".join(self.localCmds)
|
|
899
|
-
|
|
890
|
+
cmd = " ".join(self.localCmds)
|
|
900
891
|
log.info(f"call {self.solverName} locally ")
|
|
901
892
|
log.debug(f"call {self.solverName} with the following command: {cmd}")
|
|
902
893
|
|
|
903
894
|
if "win" in sys.platform:
|
|
904
|
-
as_run_file =
|
|
895
|
+
as_run_file = self.localCmds[0]
|
|
905
896
|
if not os.path.exists(as_run_file):
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
)
|
|
897
|
+
msg = "The given executable does not exist. "
|
|
898
|
+
msg += "Please enter the correct path to settings.py. "
|
|
899
|
+
msg += f"Acutal path: {as_run_file}"
|
|
900
|
+
raise InternalError(msg)
|
|
910
901
|
|
|
911
|
-
p = subprocess.Popen(
|
|
902
|
+
p = subprocess.Popen(
|
|
903
|
+
self.localCmds, cwd=self.runDir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
904
|
+
)
|
|
912
905
|
output, output_err = p.communicate()
|
|
913
906
|
p.wait()
|
|
914
907
|
|
|
@@ -940,9 +933,11 @@ class AsterCaller(FECaller):
|
|
|
940
933
|
|
|
941
934
|
|
|
942
935
|
class VegaCaller(FECaller):
|
|
936
|
+
|
|
937
|
+
solverName = "Vega"
|
|
938
|
+
|
|
943
939
|
def __init__(self, **kwargs):
|
|
944
940
|
FECaller.__init__(self, **kwargs)
|
|
945
|
-
self.solverName = "Vega"
|
|
946
941
|
|
|
947
942
|
vega_executable = kwargs.pop("vega_executable", shutil.which("vegapp"))
|
|
948
943
|
if not vega_executable:
|
|
@@ -982,9 +977,6 @@ class VegaCaller(FECaller):
|
|
|
982
977
|
versions = re.findall(r"(?<=vers : )([\w\._-]+)", res, re.RegexFlag.MULTILINE)
|
|
983
978
|
return versions
|
|
984
979
|
|
|
985
|
-
def checkResultLogFile(self, jobName):
|
|
986
|
-
return True
|
|
987
|
-
|
|
988
980
|
@staticmethod
|
|
989
981
|
def isLicenseAvailable():
|
|
990
982
|
"""
|
|
@@ -291,6 +291,15 @@ class Transformation(np.ndarray):
|
|
|
291
291
|
elif other.shape[0] == 4:
|
|
292
292
|
return np.array(np.dot(self, other))
|
|
293
293
|
|
|
294
|
+
elif other.shape[1] == 3:
|
|
295
|
+
other = np.insert(other, 3, 1, axis=1)
|
|
296
|
+
other = np.transpose(other)
|
|
297
|
+
return np.transpose(np.array(np.dot(self, other)))[:, :3]
|
|
298
|
+
|
|
299
|
+
elif other.shape[1] == 4:
|
|
300
|
+
other = np.transpose(other)
|
|
301
|
+
return np.transpose(np.array(np.dot(self, other)))
|
|
302
|
+
|
|
294
303
|
# elif other.shape[1] == 3:
|
|
295
304
|
# other = np.insert(other, 3, 1, axis=1)
|
|
296
305
|
# return np.array(self @ other.T).T[:,:3]
|
patme/mechanics/material.py
CHANGED
|
@@ -181,6 +181,12 @@ class MaterialDefinition:
|
|
|
181
181
|
self.specificHeatDefaultTemp = 20 + 273.15
|
|
182
182
|
"""Default temperature for specificHeat"""
|
|
183
183
|
|
|
184
|
+
self.GIc = None
|
|
185
|
+
"""Energy release rate mode I"""
|
|
186
|
+
|
|
187
|
+
self.GIIc = None
|
|
188
|
+
"""Energy release rate mode II"""
|
|
189
|
+
|
|
184
190
|
self.setStrength(kwargs.pop("strength", {s: 0.0 for s in self.strengthValues}))
|
|
185
191
|
|
|
186
192
|
for key in kwargs:
|
|
@@ -359,7 +365,7 @@ class MaterialDefinition:
|
|
|
359
365
|
]
|
|
360
366
|
)
|
|
361
367
|
|
|
362
|
-
redStiffVecGlo = np.
|
|
368
|
+
redStiffVecGlo = np.asmatrix(transM) * np.asmatrix(redStiffVec)
|
|
363
369
|
redStiffMatGlo = np.array(
|
|
364
370
|
[
|
|
365
371
|
[redStiffVecGlo[0, 0], redStiffVecGlo[1, 0], redStiffVecGlo[2, 0]],
|
|
@@ -376,7 +382,8 @@ class MaterialDefinition:
|
|
|
376
382
|
|
|
377
383
|
return redStiffMatGlo
|
|
378
384
|
|
|
379
|
-
|
|
385
|
+
@property
|
|
386
|
+
def specificHeat(self, T=None):
|
|
380
387
|
if T is None:
|
|
381
388
|
T = self.specificHeatDefaultTemp
|
|
382
389
|
return self.specificHeatFunction(T)
|
|
@@ -465,11 +472,11 @@ class MaterialDefinition:
|
|
|
465
472
|
newMaterialDefinition._savedModuli = {}
|
|
466
473
|
newMaterialDefinition._kPlaneStressCondition = None
|
|
467
474
|
|
|
468
|
-
stiff = np.
|
|
475
|
+
stiff = np.asmatrix(newMaterialDefinition.stiffnessMatrix)
|
|
469
476
|
|
|
470
477
|
c = np.cos(np.radians(orientationAngle))
|
|
471
478
|
s = np.sin(np.radians(orientationAngle))
|
|
472
|
-
trafoMinv = np.
|
|
479
|
+
trafoMinv = np.asmatrix(
|
|
473
480
|
np.array(
|
|
474
481
|
[
|
|
475
482
|
[c**2.0, s**2.0, 0.0, 0.0, 0.0, -2 * s * c],
|
|
@@ -522,7 +529,8 @@ class MaterialDefinition:
|
|
|
522
529
|
"""returns the thermal expansion coefficients in directions XX YY XY"""
|
|
523
530
|
return np.array([self.thermalExpansionCoeff[0], self.thermalExpansionCoeff[3], self.thermalExpansionCoeff[1]])
|
|
524
531
|
|
|
525
|
-
|
|
532
|
+
@property
|
|
533
|
+
def kPlaneStressCondition(self):
|
|
526
534
|
"""doc"""
|
|
527
535
|
if self._kPlaneStressCondition is not None:
|
|
528
536
|
return self._kPlaneStressCondition
|
|
@@ -543,15 +551,18 @@ class MaterialDefinition:
|
|
|
543
551
|
|
|
544
552
|
return self._kPlaneStressCondition
|
|
545
553
|
|
|
546
|
-
|
|
554
|
+
@property
|
|
555
|
+
def isIsotrop(self):
|
|
547
556
|
""":return: True if MaterialDefinition is isotrop. This is calculated by means of the stiffness matrix."""
|
|
548
557
|
return abs((self.stiffnessMatrix[0, 1] / self.stiffnessMatrix[1, 2]) - 1) < epsilon
|
|
549
558
|
|
|
550
|
-
|
|
559
|
+
@property
|
|
560
|
+
def isOrthotrop(self):
|
|
551
561
|
""":return: True if MaterialDefinition is orthotrop. This is calculated by means of the stiffness matrix."""
|
|
552
562
|
return not np.any(self.stiffnessMatrix[3:, :3])
|
|
553
563
|
|
|
554
|
-
|
|
564
|
+
@property
|
|
565
|
+
def stiffnessInvariants(self):
|
|
555
566
|
"""doc"""
|
|
556
567
|
|
|
557
568
|
reducedStiffness = np.array(
|
|
@@ -575,13 +586,79 @@ class MaterialDefinition:
|
|
|
575
586
|
|
|
576
587
|
return np.dot(coefficientMatrix, reducedStiffness)
|
|
577
588
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
"""see MaterialDefinition._kPlaneStressCondition"""
|
|
589
|
+
def getInSituStrength(self, isOuterPly, plyThickness, beta=2.98e-8):
|
|
590
|
+
"""Calculate in situ strength for sigma22t(R22), "sigma22c", "tau"
|
|
581
591
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
592
|
+
:param isOuterPly: flag if the ply is an outer ply
|
|
593
|
+
:param plyThickness: thickness of the ply
|
|
594
|
+
:param beta: nonlinear shear response parameter. Default value from IM7-8552
|
|
595
|
+
:return: 2-tuple: in situ transverse tension strength, in situ longitudinal shear strength
|
|
596
|
+
"""
|
|
597
|
+
moduli = self.moduli
|
|
598
|
+
E11, E22, G12, NU12, NU21 = moduli["e11"], moduli["e22"], moduli["g12"], moduli["nu12"], moduli["nu21"]
|
|
599
|
+
Yt, Sl = self.strength["sigma22t"], self.strength["tau"]
|
|
600
|
+
G2t, G2s = self.GIc, self.GIIc
|
|
601
|
+
|
|
602
|
+
telda22 = 2 * ((1 / E22) - (NU21**2 / E11))
|
|
603
|
+
scalarOrArrayMask = np.ones_like(plyThickness) # make all results have the same size as plyThickness
|
|
604
|
+
if isOuterPly:
|
|
605
|
+
Yt_outer_thin, Ys_outer_thin = self._getInsituOuterPlyStrengths(G12, G2t, G2s, plyThickness, telda22, beta)
|
|
606
|
+
return np.max([Yt_outer_thin, Yt * scalarOrArrayMask], axis=0), np.max(
|
|
607
|
+
[Ys_outer_thin, Sl * scalarOrArrayMask], axis=0
|
|
608
|
+
)
|
|
609
|
+
else:
|
|
610
|
+
Yt_inner_thick, Yt_inner_thin, Ys_inner_thin, Ys_inner_thick = self._getInSituInnerPlyStrengths(
|
|
611
|
+
G12, G2t, G2s, Yt, Sl, plyThickness, telda22, beta
|
|
612
|
+
)
|
|
613
|
+
return np.max([Yt_inner_thick, Yt_inner_thin], axis=0), np.max([Ys_inner_thin, Ys_inner_thick], axis=0)
|
|
614
|
+
|
|
615
|
+
def _getInSituInnerPlyStrengths(self, G12, G2t, G2s, Yt, Sl, plyThickness, telda22, beta):
|
|
616
|
+
scalarOrArrayMask = np.ones_like(plyThickness)
|
|
617
|
+
Yt_inner_thick = 1.12 * np.sqrt(2) * Yt * scalarOrArrayMask # For a thick embedded ply
|
|
618
|
+
Yt_inner_thin = np.sqrt(8 * G2t / (np.pi * plyThickness * telda22)) # For a thin embedded ply
|
|
619
|
+
phi = 48 * G2s / (np.pi * plyThickness)
|
|
620
|
+
Ys_inner_thin = np.sqrt(((1 + beta * phi * G12**2) ** 0.5 - 1) / (3 * beta * G12))
|
|
621
|
+
phi = 12 * Sl**2 / G12 + 18 * beta * Sl**4
|
|
622
|
+
Ys_inner_thick = np.sqrt(((1 + beta * phi * G12**2) ** 0.5 - 1) / (3 * beta * G12)) * scalarOrArrayMask
|
|
623
|
+
return Yt_inner_thick, Yt_inner_thin, Ys_inner_thin, Ys_inner_thick
|
|
624
|
+
|
|
625
|
+
def _getInsituOuterPlyStrengths(self, G12, G2t, G2s, plyThickness, telda22, beta):
|
|
626
|
+
Yt_outer_thin = 1.78 * np.sqrt(G2t / (np.pi * plyThickness * telda22))
|
|
627
|
+
phi = 24 * G2s / (np.pi * plyThickness)
|
|
628
|
+
Ys_outer_thin = np.sqrt(((1 + beta * phi * G12**2) ** 0.5 - 1) / (3 * beta * G12))
|
|
629
|
+
return Yt_outer_thin, Ys_outer_thin
|
|
630
|
+
|
|
631
|
+
def getInSituStrengthAllValues(self, plyThickness, beta=2.98e-8):
|
|
632
|
+
"""Calculate in situ strength for sigma22t(R22), "sigma22c", "tau"
|
|
633
|
+
|
|
634
|
+
:param isOuterPly: flag if the ply is an outer ply
|
|
635
|
+
:param plyThickness: thickness of the ply
|
|
636
|
+
:param beta: nonlinear shear response parameter. Default value from IM7-8552
|
|
637
|
+
:return: 2-tuple: in situ transverse tension strength, in situ longitudinal shear strength
|
|
638
|
+
"""
|
|
639
|
+
moduli = self.moduli
|
|
640
|
+
E11, E22, G12, NU12, NU21 = moduli["e11"], moduli["e22"], moduli["g12"], moduli["nu12"], moduli["nu21"]
|
|
641
|
+
Yt, Sl = self.strength["sigma22t"], self.strength["tau"]
|
|
642
|
+
G2t, G2s = self.GIc, self.GIIc
|
|
643
|
+
telda22 = 2 * ((1 / E22) - (NU21**2 / E11))
|
|
644
|
+
|
|
645
|
+
scalarOrArrayMask = np.ones_like(plyThickness) # make all results have the same size as plyThickness
|
|
646
|
+
|
|
647
|
+
result = self._getInsituOuterPlyStrengths(G12, G2t, G2s, plyThickness, telda22, beta)
|
|
648
|
+
Yt_outer_thin, Ys_outer_thin = result
|
|
649
|
+
result = self._getInSituInnerPlyStrengths(G12, G2t, G2s, Yt, Sl, plyThickness, telda22, beta)
|
|
650
|
+
Yt_inner_thick, Yt_inner_thin, Ys_inner_thin, Ys_inner_thick = result
|
|
651
|
+
|
|
652
|
+
return (
|
|
653
|
+
Yt_outer_thin,
|
|
654
|
+
Yt * scalarOrArrayMask,
|
|
655
|
+
Ys_outer_thin,
|
|
656
|
+
Sl * scalarOrArrayMask,
|
|
657
|
+
Yt_inner_thin,
|
|
658
|
+
Yt_inner_thick,
|
|
659
|
+
Ys_inner_thin,
|
|
660
|
+
Ys_inner_thick,
|
|
661
|
+
)
|
|
585
662
|
|
|
586
663
|
|
|
587
664
|
class Composite:
|
patme/service/mathutils.py
CHANGED
|
@@ -54,7 +54,8 @@ def get2DSubArrayWithPregivenOrder(arr, columnValueOrder, columnNumber=0, return
|
|
|
54
54
|
|
|
55
55
|
columnValueOrder = np.asarray(columnValueOrder)
|
|
56
56
|
arr_is_sorted = (np.diff(arr[:, columnNumber]) == 1).all()
|
|
57
|
-
|
|
57
|
+
query_ids_sorted = (np.diff(columnValueOrder) == 1).all()
|
|
58
|
+
if arr_is_sorted and query_ids_sorted:
|
|
58
59
|
ii = (columnValueOrder - int(arr[:, columnNumber][0])).astype(int)
|
|
59
60
|
else:
|
|
60
61
|
ii = None
|
patme/sshtools/cara.py
CHANGED
|
@@ -59,18 +59,26 @@ def _getClusterAuthentication():
|
|
|
59
59
|
hostKeyString = "AAAAB3NzaC1yc2EAAAADAQABAAACAQDL9y9u3D+refVuZnJJNdVeMK53EG0hfGUwuA+JyT2zOs6xOnhXhbTB0hOpORv4sd9V3mHJDf1yyIlZ/bgJCT4Znazz3amqzD7SmqGeNR8r7Z4whQY0drMpL67fthFNsqoUdjsOn+FZfWsZhy2ntMLIi4KRZ9Kaoe8Kqo3j1gej0iwq6W2+LYB69zhP1SHtT+603Qw97kAgrQeA2R71BFwUXSRzgDbPlucX8he9S4WjWZ3OTpfXksQtIN/8jGAsTw6x/4iu1ia8bjW5jc4q5qrF4UPdsRlbuByn2/QBU4XHZUcq6rZqv6KGyNqja2sZHsT7weDHo5JtYMNUzVB75SfmMigIxy3hcD6xicc5gSLQuw7e1BZsC8ld9Ku5hkL9OdXl/jkble55dO9lEKgze+y0QscBAYJKgi0FpQSMxw9SNt1IdImosIWfTT3jY3halybgWKvx85LVM86q45bk0RSSjgh1Oup87UO3GqF72zA+PX36v32WqMKoQ6ssqKjXOwSsXC1Ytf4GU7utoUXsqqFZOM/6CZp/09yPTTkkZGGsy2iUOw/1bS3uQcZi+lIpWqtEbsHYjrEOIPxofz4gl2Fo8yfQoUhKmED4XwWMnw0jwxNHy2uwBQz0ysIT4tz1ekBUh4fgO+2xhX/g6O24sLsfAGzc/I1gIUpmMaGJOQiwuw=="
|
|
60
60
|
|
|
61
61
|
"""this is the key from the file "~/.ssh/known_hosts" used by openssh on a linux machine"""
|
|
62
|
-
|
|
62
|
+
from patme.sshtools.sshcall import privateKeyFileConfig
|
|
63
|
+
|
|
64
|
+
privateKey = privateKeyFileConfig
|
|
65
|
+
if not os.path.exists(privateKey):
|
|
66
|
+
privateKey = os.path.join(os.path.expanduser("~"), ".ssh", "id_rsa")
|
|
63
67
|
|
|
64
68
|
try:
|
|
65
69
|
from sshconf import read_ssh_config
|
|
66
70
|
|
|
67
71
|
cfgfile = os.path.join(os.path.expanduser("~"), ".ssh", "config")
|
|
68
72
|
if os.path.exists(cfgfile):
|
|
73
|
+
# log.debug(f"SSH configuration file found: {cfgfile}")
|
|
69
74
|
conf = read_ssh_config(cfgfile)
|
|
70
75
|
for host in conf.hosts():
|
|
71
76
|
cara_host = conf.host(host)
|
|
72
77
|
if "cara.dlr.de" in cara_host["hostname"]:
|
|
78
|
+
# log.debug(f"CARA ssh configuration found in {cfgfile}.")
|
|
73
79
|
privateKey = cara_host["identityfile"]
|
|
80
|
+
# log.debug(f"{privateKey} is used as new private key file.")
|
|
81
|
+
|
|
74
82
|
break
|
|
75
83
|
|
|
76
84
|
except ImportError:
|
patme/sshtools/clustercaller.py
CHANGED
|
@@ -53,7 +53,6 @@ class ClusterCaller:
|
|
|
53
53
|
"""Directory where files will be copied remotely. Defaults to solverName in runRemote
|
|
54
54
|
remoteRunDir = ~/remoteBaseDir/self.runDir[-1]"""
|
|
55
55
|
self.remoteCmds = None
|
|
56
|
-
self.solverName = None
|
|
57
56
|
self.preCommands = []
|
|
58
57
|
"""List of commands that are sent to the cluster prior to the remote command.
|
|
59
58
|
It is intended to setup the environment for the call that will be done
|
|
@@ -68,6 +67,8 @@ class ClusterCaller:
|
|
|
68
67
|
self._clusterName = kwargs.pop("clusterName", "cara")
|
|
69
68
|
self.__checkClusterName(self._clusterName)
|
|
70
69
|
|
|
70
|
+
self.docker_container = kwargs.pop("docker_container", None)
|
|
71
|
+
|
|
71
72
|
if "wsl" in self.clusterName:
|
|
72
73
|
self.remoteCmds = [
|
|
73
74
|
"bash",
|
|
@@ -75,8 +76,8 @@ class ClusterCaller:
|
|
|
75
76
|
self.REMOTE_SCRIPT,
|
|
76
77
|
]
|
|
77
78
|
elif "cara" in self.clusterName:
|
|
79
|
+
self.preCommands.append(f"chmod a+x {self.REMOTE_SCRIPT}")
|
|
78
80
|
self.remoteCmds = [
|
|
79
|
-
f"chmod a+x {self.REMOTE_SCRIPT} ; ",
|
|
80
81
|
"sbatch",
|
|
81
82
|
self.REMOTE_SCRIPT,
|
|
82
83
|
]
|
|
@@ -95,12 +96,17 @@ class ClusterCaller:
|
|
|
95
96
|
"""doc"""
|
|
96
97
|
log.info(f"call {self.solverName} remotely on '{self.clusterName}'")
|
|
97
98
|
|
|
98
|
-
self.addClusterSpecificBatchCmds()
|
|
99
|
-
|
|
100
99
|
remoteRunDir = kwargs.get("remoteRunDir", None)
|
|
101
100
|
if not remoteRunDir:
|
|
102
101
|
ws_dir_name, remoteRunDir = self.getRemoteRunDir(**kwargs)
|
|
103
102
|
|
|
103
|
+
if self.docker_container is not None:
|
|
104
|
+
basedir = os.path.basename(remoteRunDir)
|
|
105
|
+
docker_mnt_dir = f"/mnt/{basedir}"
|
|
106
|
+
self.addClusterSpecificBatchCmds(jobName, docker_mnt_dir)
|
|
107
|
+
else:
|
|
108
|
+
self.addClusterSpecificBatchCmds(jobName, remoteRunDir)
|
|
109
|
+
|
|
104
110
|
# copy rundir to network folder
|
|
105
111
|
if copyFilesLocalToHost:
|
|
106
112
|
log.info(f"Copy files to remote machine '{self.clusterName}'")
|
|
@@ -115,7 +121,17 @@ class ClusterCaller:
|
|
|
115
121
|
if "wsl" not in self.clusterName:
|
|
116
122
|
command = f"cd {remoteRunDir};{remoteCmdsJoin}"
|
|
117
123
|
else:
|
|
118
|
-
|
|
124
|
+
if self.docker_container is not None:
|
|
125
|
+
if ".sh" not in remoteCmds[-1]:
|
|
126
|
+
raise Exception("Test")
|
|
127
|
+
|
|
128
|
+
remoteCmds[-1] = f"{docker_mnt_dir}/{remoteCmds[-1]}"
|
|
129
|
+
remoteCmds[0] = (
|
|
130
|
+
f"docker run -w {docker_mnt_dir} -it -v {remoteRunDir}:{docker_mnt_dir} {self.docker_container} "
|
|
131
|
+
)
|
|
132
|
+
command = " ".join(remoteCmds)
|
|
133
|
+
else:
|
|
134
|
+
command = remoteCmdsJoin
|
|
119
135
|
|
|
120
136
|
if self.preCommands:
|
|
121
137
|
precmd = ";".join(self.preCommands)
|
|
@@ -137,7 +153,6 @@ class ClusterCaller:
|
|
|
137
153
|
else:
|
|
138
154
|
|
|
139
155
|
jobId = "WSL_NOJOBID"
|
|
140
|
-
command += " > cluster.r00.log"
|
|
141
156
|
remoteRunDirAsWinPath = self.__getWSLRemoteDirAsWinPath(remoteRunDir)
|
|
142
157
|
wsl_cmd = f"wsl --cd {remoteRunDirAsWinPath} {command}"
|
|
143
158
|
p = subprocess.run(wsl_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
@@ -255,10 +270,9 @@ class ClusterCaller:
|
|
|
255
270
|
4.1 optionally extract tarfile remotely to reuse the calculation results
|
|
256
271
|
5. remove the tarfile remote"""
|
|
257
272
|
|
|
258
|
-
def getFileAndExtract(tarFileName, tarFileNamePath,
|
|
273
|
+
def getFileAndExtract(tarFileName, tarFileNamePath, remoteRunDir, copyFunc, username=None):
|
|
259
274
|
"""doc"""
|
|
260
|
-
|
|
261
|
-
copyFunc([ffile], remoteRunDir, self.runDir, "get", username=username)
|
|
275
|
+
copyFunc([tarFileName], remoteRunDir, self.runDir, "get", username=username)
|
|
262
276
|
|
|
263
277
|
with tarfile.open(tarFileNamePath) as f:
|
|
264
278
|
f.extractall(self.runDir, filter="data")
|
|
@@ -291,15 +305,11 @@ class ClusterCaller:
|
|
|
291
305
|
|
|
292
306
|
try:
|
|
293
307
|
log.info("copy result from cluster")
|
|
294
|
-
|
|
295
|
-
tarFileName = f"{prefix}.tar.gz"
|
|
296
|
-
clusterLog = f"{prefix}.log"
|
|
308
|
+
tarFileName = f"cluster.r{jobId}.tar.gz"
|
|
297
309
|
tarFileNamePath = os.path.join(self.runDir, tarFileName)
|
|
298
310
|
|
|
299
311
|
try:
|
|
300
|
-
getFileAndExtract(
|
|
301
|
-
tarFileName, tarFileNamePath, clusterLog, remoteRunDir, copyFunc, username=username
|
|
302
|
-
)
|
|
312
|
+
getFileAndExtract(tarFileName, tarFileNamePath, remoteRunDir, copyFunc, username=username)
|
|
303
313
|
|
|
304
314
|
except tarfile.ReadError:
|
|
305
315
|
msg = "Could not open the result tar file. "
|
|
@@ -312,7 +322,7 @@ class ClusterCaller:
|
|
|
312
322
|
msg += "Try again to copy and extract"
|
|
313
323
|
log.error(msg)
|
|
314
324
|
try:
|
|
315
|
-
getFileAndExtract(tarFileName, tarFileNamePath,
|
|
325
|
+
getFileAndExtract(tarFileName, tarFileNamePath, remoteRunDir)
|
|
316
326
|
except tarfile.ReadError:
|
|
317
327
|
msg = "Could not open the result tar file. "
|
|
318
328
|
msg += "Probably the calculation was cancelled."
|
|
@@ -339,7 +349,7 @@ class ClusterCaller:
|
|
|
339
349
|
log.warning(msg)
|
|
340
350
|
log.debug("error message to the above warning: " + str(exception))
|
|
341
351
|
|
|
342
|
-
def addClusterSpecificBatchCmds(self,
|
|
352
|
+
def addClusterSpecificBatchCmds(self, jobName, remoteRunDir, **kwargs):
|
|
343
353
|
"""Create bash cmds which for remote sbatch call"""
|
|
344
354
|
runScriptCmds = ["#!/bin/bash"]
|
|
345
355
|
if self.clusterName == "cara":
|
|
@@ -350,7 +360,11 @@ class ClusterCaller:
|
|
|
350
360
|
if cara_partion == "ppp":
|
|
351
361
|
slurm_args["ntasks"] = "1"
|
|
352
362
|
|
|
363
|
+
slurm_args["output"] = f"{jobName}.log"
|
|
364
|
+
|
|
353
365
|
for sarg, sval in slurm_args.items():
|
|
366
|
+
if f"--{sarg}" in self.remoteCmds:
|
|
367
|
+
continue
|
|
354
368
|
cmd = f"#SBATCH --{sarg}"
|
|
355
369
|
cmd += "" if not sval else f"={sval}"
|
|
356
370
|
runScriptCmds.append(cmd)
|
|
@@ -359,18 +373,19 @@ class ClusterCaller:
|
|
|
359
373
|
". /usr/share/lmod/lmod/init/sh",
|
|
360
374
|
". /etc/profile.d/10-dlr-modulepath.sh",
|
|
361
375
|
"module --force purge",
|
|
376
|
+
'curtime=$(date +"%Y-%m-%d %H:%M:%S")',
|
|
377
|
+
# find can only distinguish between timestamps which have seconds as highest precision
|
|
378
|
+
"sleep 1s",
|
|
362
379
|
]
|
|
363
380
|
|
|
364
|
-
runScriptCmds += [
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
'tar -czf $tarFile -C . $(find . -newermt "$curtime" -type f)',
|
|
373
|
-
]
|
|
381
|
+
runScriptCmds += [f"cd {remoteRunDir}"]
|
|
382
|
+
runScriptCmds += self.generateSolverSpecificRemoteCmds(jobName)
|
|
383
|
+
runScriptCmds += [f"touch {jobName}.log"]
|
|
384
|
+
if self.clusterName == "cara":
|
|
385
|
+
runScriptCmds += [
|
|
386
|
+
'tarFile="cluster.r$SLURM_JOB_ID.tar.gz"',
|
|
387
|
+
'tar -czf $tarFile -C . $(find . -newermt "$curtime" -type f)',
|
|
388
|
+
]
|
|
374
389
|
|
|
375
390
|
runScriptFile = os.path.join(self.runDir, self.REMOTE_SCRIPT)
|
|
376
391
|
with open(runScriptFile, "w") as f:
|
patme/sshtools/sshcall.py
CHANGED
|
@@ -56,15 +56,21 @@ def callSSH(
|
|
|
56
56
|
_expandKnownHostsFile(hostname, hostKeyString, port, keytype)
|
|
57
57
|
|
|
58
58
|
callArgs = [shutil.which("ssh")]
|
|
59
|
-
if (privateKeyFile is not None) and os.path.exists(privateKeyFile):
|
|
60
|
-
callArgs += ["-i", privateKeyFile]
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
ssh_alias = os.environ.get("PATME_SSH_ALIAS", None)
|
|
61
|
+
if ssh_alias is None:
|
|
62
|
+
if (privateKeyFile is not None) and os.path.exists(privateKeyFile):
|
|
63
|
+
callArgs += ["-i", privateKeyFile]
|
|
64
|
+
|
|
65
|
+
callArgs += ["-o", "BatchMode=true"]
|
|
66
|
+
if port:
|
|
67
|
+
callArgs += ["-p", str(port)]
|
|
68
|
+
if not username:
|
|
69
|
+
username = getpass.getuser()
|
|
70
|
+
callArgs += [f"{username}@{hostname}", remoteCommand]
|
|
71
|
+
|
|
72
|
+
else:
|
|
73
|
+
callArgs += [ssh_alias, remoteCommand]
|
|
68
74
|
|
|
69
75
|
log.info("Call ssh: " + " ".join(callArgs))
|
|
70
76
|
result = run(callArgs, stdout=PIPE, stderr=PIPE, universal_newlines=True, encoding="latin-1", errors="ignore")
|
|
@@ -114,15 +120,21 @@ def copyFilesSCP(
|
|
|
114
120
|
sendList = [f if os.path.isabs(f) else os.path.join(srcBaseDir, f) for f in files]
|
|
115
121
|
|
|
116
122
|
callArgs = [shutil.which("scp"), "-T"]
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
ssh_alias = os.environ.get("PATME_SSH_ALIAS", None)
|
|
124
|
+
if ssh_alias is None:
|
|
125
|
+
|
|
126
|
+
if (privateKeyFile is not None) and os.path.exists(privateKeyFile):
|
|
127
|
+
callArgs += ["-i", privateKeyFile]
|
|
128
|
+
|
|
129
|
+
callArgs += ["-o", "BatchMode=true"]
|
|
130
|
+
if port:
|
|
131
|
+
callArgs += ["-p", str(port)]
|
|
132
|
+
if not username:
|
|
133
|
+
username = getpass.getuser()
|
|
134
|
+
userAndHost = f"{username}@{hostname}:"
|
|
135
|
+
else:
|
|
136
|
+
userAndHost = f"{ssh_alias}:"
|
|
137
|
+
|
|
126
138
|
if mode == "put":
|
|
127
139
|
source = sendList
|
|
128
140
|
dest = userAndHost + destBaseDir
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: patme
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
4
4
|
Summary: Utilities for software builds, documentation, cluster interaction, calling fem tools, logging, exceptions and simple geometric and mechanical operations.
|
|
5
5
|
License: DLR 2025
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
License-File: LICENSES/MIT.txt
|
|
6
8
|
Keywords: build,cluster,fem,log,exception
|
|
7
9
|
Author: Sebastian Freund
|
|
8
10
|
Author-email: sebastian.freund@dlr.de
|
|
@@ -16,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
22
|
Requires-Dist: importlib_metadata
|
|
20
23
|
Requires-Dist: numpy (<2)
|
|
21
24
|
Requires-Dist: scipy
|
|
@@ -37,6 +40,7 @@ SPDX-License-Identifier: MIT
|
|
|
37
40
|
|
|
38
41
|
Utilities for basic software engineering, geometry, mechanics support and infrastructure handling.
|
|
39
42
|
|
|
43
|
+
[](https://doi.org/10.5281/zenodo.15591827)
|
|
40
44
|
[](https://gitlab.dlr.de/sy-stm/software/patme/-/commits/master)
|
|
41
45
|
[](https://ggitlab.dlr.de/sy-stm/software/patme/-/commits/master)
|
|
42
46
|
[](https://gitlab.dlr.de/sy-stm/software/patme/-/releases)
|
|
@@ -4,10 +4,10 @@ patme/buildtools/rce_releasecreator.py,sha256=AFoAOjJ4VXQNUiaQ6PQoKW29UHi7NxPOA3
|
|
|
4
4
|
patme/buildtools/release.py,sha256=PPu7on_9eXnKeytmTno5tVEzwrc_HUqLmY0b3UQcQhs,662
|
|
5
5
|
patme/femtools/__init__.py,sha256=h2Mna7Uv4eN1RIdSgsVDDZENkIB5EDkVRtkx3GWSgkU,123
|
|
6
6
|
patme/femtools/abqmsgfilechecker.py,sha256=tJz57UIJDkAia0ftrvwrKo2lJxfXLgOvOg9XA85kVto,4998
|
|
7
|
-
patme/femtools/fecall.py,sha256=
|
|
7
|
+
patme/femtools/fecall.py,sha256=EkksGoU7cZ-1IiGpfgc67ELmhzaehzPbbR7-l-2Ihbk,39011
|
|
8
8
|
patme/geometry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
patme/geometry/area.py,sha256=g_eetXrVz59ZT5evC4W3ONwBmN_5CXpSp3OIvkIBeqA,4663
|
|
10
|
-
patme/geometry/coordinatesystem.py,sha256=
|
|
10
|
+
patme/geometry/coordinatesystem.py,sha256=135E6Ta7ugZiSYk6G-Vm_lT9mdEdjBplhxrr9Fgdqto,26441
|
|
11
11
|
patme/geometry/intersect.py,sha256=Q-sZoVIMPMKtSv1oqojpdWf74nxToE64FBntz2zRMJ8,12459
|
|
12
12
|
patme/geometry/line.py,sha256=h1Pnsa-LJfrD956VnGkA8TlKTiZaQtu5NQU9EjU76L4,6743
|
|
13
13
|
patme/geometry/misc.py,sha256=0Kw0DM5nY8g44NfEf57l4nfxwVaLQOi70DoJmYwGu8M,15852
|
|
@@ -19,7 +19,7 @@ patme/geometry/transformations.py,sha256=A3W0VE1XQ6VoMtEdaUeqCVFXJZ1E5kWzVBQHoW-
|
|
|
19
19
|
patme/geometry/translate.py,sha256=BtYJGp9X-SQfLTAaUZvzZL-8lNFP5vxysP1_wqq1ZYA,4192
|
|
20
20
|
patme/mechanics/__init__.py,sha256=sNpRBARPNJs19hdEIq2RVTRNzYgCxmGJ-ZeG7wiapsM,201
|
|
21
21
|
patme/mechanics/loads.py,sha256=nmQPEQqO27Rc7OcIbqKqqGidcgvQyWLqb5rlCt86zcE,15218
|
|
22
|
-
patme/mechanics/material.py,sha256=
|
|
22
|
+
patme/mechanics/material.py,sha256=jQhjZl5n688RMv465rWtib3DPTpgpkwKUJ95aRgBZzY,51325
|
|
23
23
|
patme/service/__init__.py,sha256=BFFNn669dQChgknQaFb9Le42MGHbFh8u33FVwQPDh9A,202
|
|
24
24
|
patme/service/decorators.py,sha256=FY9_94Qi5t9AQzGc61MuuKkVh90YCKMd6WcUvByeNlo,2587
|
|
25
25
|
patme/service/duration.py,sha256=Cy4K42JiOA2aDqIrRuV4RUMdD5xfrmecDJzlAVsz5jI,2163
|
|
@@ -28,19 +28,19 @@ patme/service/exceptions.py,sha256=uBygOH04uni31WPzf1If-N33gKgDL3CZwOX6nPLMQXc,7
|
|
|
28
28
|
patme/service/io/__init__.py,sha256=OPhWsSvs9JnL6quWtiFOZwQM7AynJi8IWdrR_XML57Q,94
|
|
29
29
|
patme/service/io/basewriter.py,sha256=wzb9uk1pEfWMPYWEO-I54XwGyE4KJXiWv_bI1j39kfI,3767
|
|
30
30
|
patme/service/logger.py,sha256=efa2ONcny_JEyc9mpiYQAgT58j6FyZTtozkHHkX4n4s,13115
|
|
31
|
-
patme/service/mathutils.py,sha256=
|
|
31
|
+
patme/service/mathutils.py,sha256=W9D3z-mTDFDS_sgg2yugzxZyzQjR97gQ8t0haAvHtxk,3535
|
|
32
32
|
patme/service/misc.py,sha256=-SVtPzhzj5cQd0KAAqFeErCqhB47i0sTUWHrFSX3Pu4,2115
|
|
33
33
|
patme/service/moveimports.py,sha256=chbnwSRtUfFam5m5TZfGhJg4FfD3E_v15zJIsyhkt6g,8463
|
|
34
34
|
patme/service/stringutils.py,sha256=n0VQ-53rysm7YpCA79rkhqRS6TXHDwYDO6GSETZksdY,16093
|
|
35
35
|
patme/service/systemutils.py,sha256=4ZYv4hthbFj42wFV-gjXNe6Y5m4yEAOA74dnxPAkVYk,10927
|
|
36
36
|
patme/sshtools/__init__.py,sha256=OPhWsSvs9JnL6quWtiFOZwQM7AynJi8IWdrR_XML57Q,94
|
|
37
|
-
patme/sshtools/cara.py,sha256=
|
|
38
|
-
patme/sshtools/clustercaller.py,sha256=
|
|
37
|
+
patme/sshtools/cara.py,sha256=jF6pKg1iKaj0a_-i68YUSDQgqpeTaXR2H0iQVDccRRE,17431
|
|
38
|
+
patme/sshtools/clustercaller.py,sha256=DXBDN84KXzcbMNCVFOm8mmBI1oe7r9jw9i0zOnbhUcQ,17885
|
|
39
39
|
patme/sshtools/facluster.py,sha256=-QwXI9YoZz8ynGUnpRDchG7fFO1F5MXZShf9h_GaRs8,14710
|
|
40
|
-
patme/sshtools/sshcall.py,sha256=
|
|
41
|
-
patme-0.4.
|
|
42
|
-
patme-0.4.
|
|
43
|
-
patme-0.4.
|
|
44
|
-
patme-0.4.
|
|
45
|
-
patme-0.4.
|
|
46
|
-
patme-0.4.
|
|
40
|
+
patme/sshtools/sshcall.py,sha256=nY4B6y7sd-RTe2vlBJxHJVs1GYdKhK4phv46JW0QPzI,6680
|
|
41
|
+
patme-0.4.5.dist-info/METADATA,sha256=z_JEFJwm16APusIFiRxPg3hSdi9EqRPthrH9xEiBZDs,5040
|
|
42
|
+
patme-0.4.5.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
43
|
+
patme-0.4.5.dist-info/entry_points.txt,sha256=Tx3XSAm8TGSy5jbuexhI1nLhNdJKJqhw6XY7pb8_MOM,59
|
|
44
|
+
patme-0.4.5.dist-info/licenses/LICENSE,sha256=m84IbJjjgyJWly5cPCFRrqcWC6wFGbQZSthKGwXy00I,1074
|
|
45
|
+
patme-0.4.5.dist-info/licenses/LICENSES/MIT.txt,sha256=uF3NPkU9BZglUsUrX8ngvdbSPG-OhEuYSoivMlcLDMA,1078
|
|
46
|
+
patme-0.4.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|