patme 0.4.4__tar.gz → 0.4.5__tar.gz

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.

Files changed (46) hide show
  1. {patme-0.4.4 → patme-0.4.5}/PKG-INFO +6 -2
  2. {patme-0.4.4 → patme-0.4.5}/README.md +1 -0
  3. {patme-0.4.4 → patme-0.4.5}/pyproject.toml +1 -1
  4. {patme-0.4.4 → patme-0.4.5}/src/patme/femtools/fecall.py +118 -126
  5. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/coordinatesystem.py +9 -0
  6. {patme-0.4.4 → patme-0.4.5}/src/patme/mechanics/material.py +91 -14
  7. {patme-0.4.4 → patme-0.4.5}/src/patme/service/mathutils.py +2 -1
  8. {patme-0.4.4 → patme-0.4.5}/src/patme/sshtools/cara.py +9 -1
  9. {patme-0.4.4 → patme-0.4.5}/src/patme/sshtools/clustercaller.py +42 -27
  10. {patme-0.4.4 → patme-0.4.5}/src/patme/sshtools/sshcall.py +29 -17
  11. {patme-0.4.4 → patme-0.4.5}/LICENSE +0 -0
  12. {patme-0.4.4 → patme-0.4.5}/LICENSES/MIT.txt +0 -0
  13. {patme-0.4.4 → patme-0.4.5}/src/fa_pyutils.py +0 -0
  14. {patme-0.4.4 → patme-0.4.5}/src/patme/__init__.py +0 -0
  15. {patme-0.4.4 → patme-0.4.5}/src/patme/buildtools/__init__.py +0 -0
  16. {patme-0.4.4 → patme-0.4.5}/src/patme/buildtools/rce_releasecreator.py +0 -0
  17. {patme-0.4.4 → patme-0.4.5}/src/patme/buildtools/release.py +0 -0
  18. {patme-0.4.4 → patme-0.4.5}/src/patme/femtools/__init__.py +0 -0
  19. {patme-0.4.4 → patme-0.4.5}/src/patme/femtools/abqmsgfilechecker.py +0 -0
  20. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/__init__.py +0 -0
  21. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/area.py +0 -0
  22. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/intersect.py +0 -0
  23. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/line.py +0 -0
  24. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/misc.py +0 -0
  25. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/plane.py +0 -0
  26. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/rotate.py +0 -0
  27. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/scale.py +0 -0
  28. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/shape2d.py +0 -0
  29. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/transformations.py +0 -0
  30. {patme-0.4.4 → patme-0.4.5}/src/patme/geometry/translate.py +0 -0
  31. {patme-0.4.4 → patme-0.4.5}/src/patme/mechanics/__init__.py +0 -0
  32. {patme-0.4.4 → patme-0.4.5}/src/patme/mechanics/loads.py +0 -0
  33. {patme-0.4.4 → patme-0.4.5}/src/patme/service/__init__.py +0 -0
  34. {patme-0.4.4 → patme-0.4.5}/src/patme/service/decorators.py +0 -0
  35. {patme-0.4.4 → patme-0.4.5}/src/patme/service/duration.py +0 -0
  36. {patme-0.4.4 → patme-0.4.5}/src/patme/service/exceptionhook.py +0 -0
  37. {patme-0.4.4 → patme-0.4.5}/src/patme/service/exceptions.py +0 -0
  38. {patme-0.4.4 → patme-0.4.5}/src/patme/service/io/__init__.py +0 -0
  39. {patme-0.4.4 → patme-0.4.5}/src/patme/service/io/basewriter.py +0 -0
  40. {patme-0.4.4 → patme-0.4.5}/src/patme/service/logger.py +0 -0
  41. {patme-0.4.4 → patme-0.4.5}/src/patme/service/misc.py +0 -0
  42. {patme-0.4.4 → patme-0.4.5}/src/patme/service/moveimports.py +0 -0
  43. {patme-0.4.4 → patme-0.4.5}/src/patme/service/stringutils.py +0 -0
  44. {patme-0.4.4 → patme-0.4.5}/src/patme/service/systemutils.py +0 -0
  45. {patme-0.4.4 → patme-0.4.5}/src/patme/sshtools/__init__.py +0 -0
  46. {patme-0.4.4 → patme-0.4.5}/src/patme/sshtools/facluster.py +0 -0
@@ -1,8 +1,10 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: patme
3
- Version: 0.4.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
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.15591827.svg)](https://doi.org/10.5281/zenodo.15591827)
40
44
  [![pipeline status](https://gitlab.dlr.de/sy-stm/software/patme/badges/master/pipeline.svg)](https://gitlab.dlr.de/sy-stm/software/patme/-/commits/master)
41
45
  [![coverage report](https://gitlab.dlr.de/sy-stm/software/patme/badges/master/coverage.svg)](https://ggitlab.dlr.de/sy-stm/software/patme/-/commits/master)
42
46
  [![Latest Release](https://gitlab.dlr.de/sy-stm/software/patme/-/badges/release.svg)](https://gitlab.dlr.de/sy-stm/software/patme/-/releases)
@@ -8,6 +8,7 @@ SPDX-License-Identifier: MIT
8
8
 
9
9
  Utilities for basic software engineering, geometry, mechanics support and infrastructure handling.
10
10
 
11
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.15591827.svg)](https://doi.org/10.5281/zenodo.15591827)
11
12
  [![pipeline status](https://gitlab.dlr.de/sy-stm/software/patme/badges/master/pipeline.svg)](https://gitlab.dlr.de/sy-stm/software/patme/-/commits/master)
12
13
  [![coverage report](https://gitlab.dlr.de/sy-stm/software/patme/badges/master/coverage.svg)](https://ggitlab.dlr.de/sy-stm/software/patme/-/commits/master)
13
14
  [![Latest Release](https://gitlab.dlr.de/sy-stm/software/patme/-/badges/release.svg)](https://gitlab.dlr.de/sy-stm/software/patme/-/releases)
@@ -4,7 +4,7 @@
4
4
 
5
5
  [project]
6
6
  name = "patme"
7
- version = "0.4.4"
7
+ version = "0.4.5"
8
8
  description = "Utilities for software builds, documentation, cluster interaction, calling fem tools, logging, exceptions and simple geometric and mechanical operations."
9
9
  license = "DLR 2025"
10
10
  authors = [
@@ -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 = os.path.splitext(os.path.basename(self.feFilename))[0]
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
- doRemoteCall = False
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 = kwargs.pop("toStderr", None)
188
- if toStderr and isinstance(toStderr, str):
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=%s" % os.path.basename(os.path.splitext(self.feFilename)[0]),
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 os.path.splitext(os.path.basename(self.feFilename))[0]
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/easybuild",
588
- "module load ABAQUS/2023",
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
- self.solverName = "AbaqusPython"
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
- pyArguments = kwargs.pop("arguments", [])
618
-
619
- callParams = ["python", self.pythonScriptPath] + pyArguments
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
- baseFile, _ = os.path.splitext(self.feFilename)
622
- callParams += [">", f"{baseFile}.msg"]
631
+ callParams += kwargs.pop("arguments", [])
623
632
 
624
- self.localCmds = [abaqusPath] + callParams
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] = pyScriptBaseFile
638
- callParms[-1] = f"{baseFile}.msg"
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/easybuild",
644
- "module load ABAQUS/2023",
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(AbaqusCaller):
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
- FECaller.__init__(self, **kwargs)
675
- self.solverName = "AbaqusCaePython"
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.append(f"b2000++ {baseFile}.mdl")
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
- resDir = self.feFilename.replace(".mdl", ".b2m")
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", baseDir)
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
- class B2MToPickleConverterCARA(B2000ppCaller):
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
- baseFileInDir = os.path.basename(self.feFilename)
850
- baseFile, _ = os.path.splitext(baseFileInDir)
851
-
852
- pyScriptBaseDir = os.path.dirname(self.pythonScriptPath)
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
- callParms = self.localCmds[:]
859
- callParms[1] = pyScriptBaseFile
860
- callParms[-1] = f"{baseFile}.msg"
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
- runScriptCmds = [
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 python-3.10",
869
- "module load b2000++/4.6.3",
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
- runScriptCmds += [" ".join(callParms)]
873
- return runScriptCmds
865
+ else:
866
+ raise Exception("Unknown cluster name")
874
867
 
875
- def checkResultLogFile(self, jobName):
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 = [f"{asterBat} --run {self.feFilename}"]
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
- if "win" in sys.platform:
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 = re.search("(.*) --run", cmd).group(1)
895
+ as_run_file = self.localCmds[0]
905
896
  if not os.path.exists(as_run_file):
906
- raise InternalError(
907
- "The given executable does not exist. Please enter the correct path to settings.py. "
908
- + f"Acutal path: {as_run_file}"
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(cmd, cwd=self.runDir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
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]
@@ -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.mat(transM) * np.mat(redStiffVec)
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
- def getSpecificHeat(self, T=None):
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.mat(newMaterialDefinition.stiffnessMatrix)
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.mat(
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
- def _getkPlaneStressCondition(self):
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
- def _getIsIsotrop(self):
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
- def _getIsOrthotrop(self):
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
- def _getStiffnessInvariants(self):
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
- stiffnessInvariants = property(fget=_getStiffnessInvariants)
579
- kPlaneStressCondition = property(fget=_getkPlaneStressCondition)
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
- isIsotrop = property(fget=_getIsIsotrop)
583
- isOrthotrop = property(fget=_getIsOrthotrop)
584
- specificHeat = property(fget=getSpecificHeat)
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:
@@ -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
- if arr_is_sorted:
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
@@ -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
- privateKey = os.path.join(os.path.expanduser("~"), ".ssh", "id_rsa")
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:
@@ -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
- command = remoteCmdsJoin
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, clusterLog, remoteRunDir, copyFunc, username=None):
273
+ def getFileAndExtract(tarFileName, tarFileNamePath, remoteRunDir, copyFunc, username=None):
259
274
  """doc"""
260
- for ffile in [tarFileName, clusterLog]:
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
- prefix = f"cluster.r{jobId}"
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, clusterLog, remoteRunDir)
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, *args, **kwargs):
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
- 'curtime=$(date +"%Y-%m-%d %H:%M:%S")',
366
- # find can only distinguish between timestamps which have seconds as highest precision
367
- "sleep 1s",
368
- 'tarFile="cluster.r$SLURM_JOB_ID.tar.gz"',
369
- ]
370
- runScriptCmds += self.generateSolverSpecificRemoteCmds()
371
- runScriptCmds += [
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:
@@ -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
- callArgs += ["-o", "BatchMode=true"]
63
- if port:
64
- callArgs += ["-p", str(port)]
65
- if not username:
66
- username = getpass.getuser()
67
- callArgs += [f"{username}@{hostname}", remoteCommand]
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
- if (privateKeyFile is not None) and os.path.exists(privateKeyFile):
118
- callArgs += ["-i", privateKeyFile]
119
-
120
- callArgs += ["-o", "BatchMode=true"]
121
- if port:
122
- callArgs += ["-p", str(port)]
123
- if not username:
124
- username = getpass.getuser()
125
- userAndHost = f"{username}@{hostname}:"
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes