patme 0.4.4__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.

Files changed (46) hide show
  1. patme/__init__.py +52 -0
  2. patme/buildtools/__init__.py +7 -0
  3. patme/buildtools/rce_releasecreator.py +336 -0
  4. patme/buildtools/release.py +26 -0
  5. patme/femtools/__init__.py +5 -0
  6. patme/femtools/abqmsgfilechecker.py +137 -0
  7. patme/femtools/fecall.py +1092 -0
  8. patme/geometry/__init__.py +0 -0
  9. patme/geometry/area.py +124 -0
  10. patme/geometry/coordinatesystem.py +635 -0
  11. patme/geometry/intersect.py +284 -0
  12. patme/geometry/line.py +183 -0
  13. patme/geometry/misc.py +420 -0
  14. patme/geometry/plane.py +464 -0
  15. patme/geometry/rotate.py +244 -0
  16. patme/geometry/scale.py +152 -0
  17. patme/geometry/shape2d.py +50 -0
  18. patme/geometry/transformations.py +1831 -0
  19. patme/geometry/translate.py +139 -0
  20. patme/mechanics/__init__.py +4 -0
  21. patme/mechanics/loads.py +435 -0
  22. patme/mechanics/material.py +1260 -0
  23. patme/service/__init__.py +7 -0
  24. patme/service/decorators.py +85 -0
  25. patme/service/duration.py +96 -0
  26. patme/service/exceptionhook.py +104 -0
  27. patme/service/exceptions.py +36 -0
  28. patme/service/io/__init__.py +3 -0
  29. patme/service/io/basewriter.py +122 -0
  30. patme/service/logger.py +375 -0
  31. patme/service/mathutils.py +108 -0
  32. patme/service/misc.py +71 -0
  33. patme/service/moveimports.py +217 -0
  34. patme/service/stringutils.py +419 -0
  35. patme/service/systemutils.py +290 -0
  36. patme/sshtools/__init__.py +3 -0
  37. patme/sshtools/cara.py +435 -0
  38. patme/sshtools/clustercaller.py +420 -0
  39. patme/sshtools/facluster.py +350 -0
  40. patme/sshtools/sshcall.py +168 -0
  41. patme-0.4.4.dist-info/LICENSE +21 -0
  42. patme-0.4.4.dist-info/LICENSES/MIT.txt +9 -0
  43. patme-0.4.4.dist-info/METADATA +168 -0
  44. patme-0.4.4.dist-info/RECORD +46 -0
  45. patme-0.4.4.dist-info/WHEEL +4 -0
  46. patme-0.4.4.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,420 @@
1
+ # SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR)
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """
6
+ Created on 25.01.2017
7
+
8
+ @author: freu_se
9
+ """
10
+ import getpass
11
+ import os
12
+ import re
13
+ import shutil
14
+ import subprocess
15
+ import tarfile
16
+ import zlib
17
+ from datetime import datetime
18
+
19
+ from patme.service.duration import Duration
20
+ from patme.service.logger import log
21
+ from patme.service.systemutils import dos2unix, tarGzDirectory
22
+ from patme.sshtools.cara import copyClusterFilesSCP, get_default_slurm_args, sshCluster, sshClusterJob
23
+ from patme.sshtools.sshcall import callSSH
24
+
25
+ duration = Duration()
26
+
27
+
28
+ class ClusterCaller:
29
+ """This class represents a general interface to call several fe and other functions on the cluster.
30
+
31
+ The remote jobs are performed by copying the files of the local input directory to the
32
+ cluster on "\\\\cluster.fa.bs.dlr.de\\<username>\\job\\<runDirName>".
33
+ Then the program creates an ssh connection to the cluster. More information about
34
+ the ssh connection can be found in service.utilities.callSSH.
35
+ After the completion of the job the result is copied back to the local runDir.
36
+
37
+ :param runDir: absolute or relative path to the folder where the fe run should be executed
38
+ """
39
+
40
+ REMOTE_SCRIPT = "run_sbatch.sh"
41
+
42
+ clusterFuncDict = {
43
+ "cara": {
44
+ "copyClusterFilesSCP": copyClusterFilesSCP,
45
+ "sshCluster": sshCluster,
46
+ "sshClusterJob": sshClusterJob,
47
+ },
48
+ }
49
+
50
+ def __init__(self, runDir, **kwargs):
51
+ self.runDir = runDir
52
+ self.remoteBaseDir = None
53
+ """Directory where files will be copied remotely. Defaults to solverName in runRemote
54
+ remoteRunDir = ~/remoteBaseDir/self.runDir[-1]"""
55
+ self.remoteCmds = None
56
+ self.solverName = None
57
+ self.preCommands = []
58
+ """List of commands that are sent to the cluster prior to the remote command.
59
+ It is intended to setup the environment for the call that will be done
60
+ """
61
+ self.ignorePatternsHostToLocal = {".svn"}
62
+ """Files and folders that shall not copied from the cluster to the local machine on remote runs."""
63
+
64
+ self.reuseResults = False
65
+ """If True, the resulting tar.gz file is also extracted on the remote machine.
66
+ This way, the result folder can be reused on a next remote call without copying inputs."""
67
+
68
+ self._clusterName = kwargs.pop("clusterName", "cara")
69
+ self.__checkClusterName(self._clusterName)
70
+
71
+ if "wsl" in self.clusterName:
72
+ self.remoteCmds = [
73
+ "bash",
74
+ "-i",
75
+ self.REMOTE_SCRIPT,
76
+ ]
77
+ elif "cara" in self.clusterName:
78
+ self.remoteCmds = [
79
+ f"chmod a+x {self.REMOTE_SCRIPT} ; ",
80
+ "sbatch",
81
+ self.REMOTE_SCRIPT,
82
+ ]
83
+
84
+ def _getClusterFunc(self, funcName):
85
+ """doc"""
86
+ if self.clusterName not in self.clusterFuncDict:
87
+ raise Exception(f"Unknown cluster {self.clusterName}")
88
+
89
+ if funcName not in self.clusterFuncDict[self.clusterName]:
90
+ raise Exception(f"Unknown cluster func {funcName}")
91
+
92
+ return self.clusterFuncDict[self.clusterName][funcName]
93
+
94
+ def runRemote(self, copyFilesLocalToHost=True, jobName="job", **kwargs):
95
+ """doc"""
96
+ log.info(f"call {self.solverName} remotely on '{self.clusterName}'")
97
+
98
+ self.addClusterSpecificBatchCmds()
99
+
100
+ remoteRunDir = kwargs.get("remoteRunDir", None)
101
+ if not remoteRunDir:
102
+ ws_dir_name, remoteRunDir = self.getRemoteRunDir(**kwargs)
103
+
104
+ # copy rundir to network folder
105
+ if copyFilesLocalToHost:
106
+ log.info(f"Copy files to remote machine '{self.clusterName}'")
107
+ with log.switchLevelTemp(log.WARN):
108
+ self._transferInputToCluster(remoteRunDir, **kwargs)
109
+
110
+ # Replace <jobName> two times since it used two times in the command list
111
+ remoteCmds = [re.sub("<jobName>", jobName, elem) for elem in self.remoteCmds]
112
+
113
+ # change to run dir of fe call
114
+ remoteCmdsJoin = " ".join(remoteCmds)
115
+ if "wsl" not in self.clusterName:
116
+ command = f"cd {remoteRunDir};{remoteCmdsJoin}"
117
+ else:
118
+ command = remoteCmdsJoin
119
+
120
+ if self.preCommands:
121
+ precmd = ";".join(self.preCommands)
122
+ command = f"{precmd};{command}"
123
+
124
+ log.debug(f'Call "{self.solverName}" with the following command:')
125
+ log.debug(f"{command}", longMesageDelim=";")
126
+
127
+ if "cara" in self.clusterName:
128
+ # =======================================================================
129
+ # run remote
130
+ # =======================================================================
131
+ if self.clusterName not in self.clusterFuncDict:
132
+ raise Exception(f"Unknown cluster {self.clusterName}")
133
+
134
+ sshClusterFunc = self._getClusterFunc("sshClusterJob")
135
+
136
+ jobId = sshClusterFunc(command, printOutput=False, **kwargs)
137
+ else:
138
+
139
+ jobId = "WSL_NOJOBID"
140
+ command += " > cluster.r00.log"
141
+ remoteRunDirAsWinPath = self.__getWSLRemoteDirAsWinPath(remoteRunDir)
142
+ wsl_cmd = f"wsl --cd {remoteRunDirAsWinPath} {command}"
143
+ p = subprocess.run(wsl_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
144
+
145
+ log.debug(f"Transfer files from remote to local working dir")
146
+ with log.switchLevelTemp(log.WARN):
147
+ self._transferFromCluster(remoteRunDir, jobId, **kwargs)
148
+
149
+ self.cleanRemoteDir(ws_dir_name, remoteRunDir, **kwargs)
150
+
151
+ log.info(f"Remote call {self.solverName} with jobId {str(jobId)} done")
152
+
153
+ def cleanRemoteDir(self, ws_name, remoteRunDir, **kwargs):
154
+ """Clean scratch directory and release scratch"""
155
+ if self.clusterName == "cara":
156
+
157
+ from patme.sshtools.cara import _getClusterAuthentication
158
+
159
+ hostname, hostKeyString, privateKey = _getClusterAuthentication()
160
+ username = kwargs.get("username", getpass.getuser())
161
+ cmd = f"rm -rf {remoteRunDir}/*;ws_release {ws_name}"
162
+ _ = callSSH(hostname, cmd, privateKey, hostKeyString=hostKeyString, username=username, printOutput=False)
163
+
164
+ elif "wsl" in self.clusterName:
165
+
166
+ shutil.rmtree(self.__getWSLRemoteDirAsWinPath(remoteRunDir))
167
+
168
+ def getRemoteRunDir(self, **kwargs):
169
+ """doc"""
170
+ if self.clusterName == "cara":
171
+
172
+ from patme.sshtools.cara import _getClusterAuthentication
173
+
174
+ hostname, hostKeyString, privateKey = _getClusterAuthentication()
175
+ username = kwargs.get("username", getpass.getuser())
176
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
177
+ ws_dir_name = f"run_{timestamp}"
178
+ cmd = f"ws_allocate {ws_dir_name}"
179
+ log.info("Allocate workspace: " + ws_dir_name)
180
+ with log.switchLevelTemp(log.WARN):
181
+ scratch_dir = callSSH(
182
+ hostname, cmd, privateKey, hostKeyString=hostKeyString, username=username, printOutput=False
183
+ )
184
+
185
+ remoteRunDir = scratch_dir.strip()
186
+
187
+ elif "wsl" in self.clusterName:
188
+
189
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
190
+ ws_dir_name = f"run_{timestamp}"
191
+ remoteRunDir = f"~/tmp/{ws_dir_name}"
192
+ os.makedirs(self.__getWSLRemoteDirAsWinPath(remoteRunDir))
193
+
194
+ else:
195
+ raise Exception(f"Cluster with name {self.clusterName} not supported!")
196
+
197
+ return ws_dir_name, remoteRunDir
198
+
199
+ def _transferInputToCluster(self, remoteRunDir, **kwargs):
200
+ """Transfer the input to the cluster by performing 4 steps.
201
+
202
+ 1. tar.gz the whole input directory
203
+ 2. transfer this tarfile to the cluster via sftp
204
+ 3. extract the file on the cluster
205
+ 4. remove the tarfiles on both ends
206
+ """
207
+ log.info(f"Copy files to remote machine: {remoteRunDir}")
208
+ tarFileName = "clusterInput.tar.gz"
209
+ tarFileNameFull = tarGzDirectory(self.runDir, tarFileName)
210
+
211
+ if "cara" in self.clusterName:
212
+ username = kwargs.get("username", getpass.getuser())
213
+ if self.clusterName not in self.clusterFuncDict:
214
+ raise Exception(f"Unknown cluster {self.clusterName}")
215
+
216
+ clusterFuncCopy = self._getClusterFunc("copyClusterFilesSCP")
217
+ sshClusterFunc = self._getClusterFunc("sshCluster")
218
+
219
+ clusterFuncCopy(
220
+ [tarFileName], srcBaseDir=self.runDir, destBaseDir=remoteRunDir, mode="put", username=username
221
+ )
222
+
223
+ # unzip files at cluster and remove zip files on both sides
224
+ extract = [f"cd {remoteRunDir}", f"tar -xf {tarFileName}", f"rm {tarFileName}"]
225
+
226
+ sshClusterFunc(";".join(extract), printOutput=False, username=username)
227
+
228
+ elif "wsl" in self.clusterName:
229
+
230
+ remoteRunDirAsWinPath = self.__getWSLRemoteDirAsWinPath(remoteRunDir)
231
+ shutil.copy(tarFileNameFull, remoteRunDirAsWinPath)
232
+
233
+ cmd = f"wsl --cd {remoteRunDir} tar -xf {tarFileName} -C .;rm {tarFileName}"
234
+ subprocess.run(cmd, shell=True)
235
+
236
+ else:
237
+ raise NotImplementedError(f"Unknown cluster name or wsl: {self.clusterName}")
238
+
239
+ os.remove(os.path.join(self.runDir, tarFileName))
240
+
241
+ log.debug(f"Copy files to remote machine '{self.clusterName}' done")
242
+
243
+ def __getWSLRemoteDirAsWinPath(self, remoteDir):
244
+ """doc"""
245
+ p = subprocess.run(["wsl", "wslpath", "-w", remoteDir], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
246
+ return p.stdout.decode().strip()
247
+
248
+ def _transferFromCluster(self, remoteRunDir, jobId, **kwargs):
249
+ """Transfer the calculation result from the cluster by performing these steps.
250
+
251
+ 1. copy the result tar.gz file to self.runDir
252
+ 2. extract the file locally
253
+ 3. extract tar file remotely if the remote folder should be reused
254
+ 4. remove the tarfile locally
255
+ 4.1 optionally extract tarfile remotely to reuse the calculation results
256
+ 5. remove the tarfile remote"""
257
+
258
+ def getFileAndExtract(tarFileName, tarFileNamePath, clusterLog, remoteRunDir, copyFunc, username=None):
259
+ """doc"""
260
+ for ffile in [tarFileName, clusterLog]:
261
+ copyFunc([ffile], remoteRunDir, self.runDir, "get", username=username)
262
+
263
+ with tarfile.open(tarFileNamePath) as f:
264
+ f.extractall(self.runDir, filter="data")
265
+
266
+ if "wsl" in self.clusterName:
267
+
268
+ remoteRunDirAsWinPath = self.__getWSLRemoteDirAsWinPath(remoteRunDir)
269
+ dir_creation_time = os.path.getctime(remoteRunDirAsWinPath)
270
+ for dirpath, _, filenames in os.walk(remoteRunDirAsWinPath):
271
+ for filename in filenames:
272
+
273
+ filepath = os.path.join(dirpath, filename)
274
+ try:
275
+ file_mtime = os.path.getmtime(filepath)
276
+ # Compare the file's modification time with the directory's creation time
277
+ if file_mtime > dir_creation_time:
278
+ rel_dir = os.path.relpath(dirpath, remoteRunDirAsWinPath)
279
+ dirInRunDir = os.path.join(self.runDir, rel_dir)
280
+ os.makedirs(dirInRunDir, exist_ok=True)
281
+ shutil.copy(filepath, dirInRunDir)
282
+
283
+ except FileNotFoundError:
284
+ # Skip files that might disappear during walk
285
+ continue
286
+
287
+ else:
288
+ username = kwargs.get("username", getpass.getuser())
289
+ copyFunc = self._getClusterFunc("copyClusterFilesSCP")
290
+ sshClusterFunc = self._getClusterFunc("sshCluster")
291
+
292
+ try:
293
+ log.info("copy result from cluster")
294
+ prefix = f"cluster.r{jobId}"
295
+ tarFileName = f"{prefix}.tar.gz"
296
+ clusterLog = f"{prefix}.log"
297
+ tarFileNamePath = os.path.join(self.runDir, tarFileName)
298
+
299
+ try:
300
+ getFileAndExtract(
301
+ tarFileName, tarFileNamePath, clusterLog, remoteRunDir, copyFunc, username=username
302
+ )
303
+
304
+ except tarfile.ReadError:
305
+ msg = "Could not open the result tar file. "
306
+ msg += "Probably the calculation was cancelled."
307
+ log.error(msg)
308
+ return # retruning to make a local calculation possible
309
+
310
+ except zlib.error:
311
+ msg = "Got error while extracting result file. "
312
+ msg += "Try again to copy and extract"
313
+ log.error(msg)
314
+ try:
315
+ getFileAndExtract(tarFileName, tarFileNamePath, clusterLog, remoteRunDir)
316
+ except tarfile.ReadError:
317
+ msg = "Could not open the result tar file. "
318
+ msg += "Probably the calculation was cancelled."
319
+ log.error(msg)
320
+ return # retruning to make a local calculation possible
321
+
322
+ os.remove(tarFileNamePath)
323
+
324
+ cleanupCommand = f"cd {remoteRunDir}"
325
+ if self.reuseResults:
326
+ cleanupCommand += f";tar -xf {tarFileName}"
327
+
328
+ cleanupCommand += f";rm {tarFileName}"
329
+
330
+ sshClusterFunc(cleanupCommand, printOutput=False, username=username)
331
+ log.debug("copy result from cluster done")
332
+
333
+ except OSError as exception:
334
+
335
+ msg = "Retrieved IOError when copying results from remote computer. "
336
+ msg += "Maybe some items are not copied properly. "
337
+ msg += "See debug.log for more details."
338
+
339
+ log.warning(msg)
340
+ log.debug("error message to the above warning: " + str(exception))
341
+
342
+ def addClusterSpecificBatchCmds(self, *args, **kwargs):
343
+ """Create bash cmds which for remote sbatch call"""
344
+ runScriptCmds = ["#!/bin/bash"]
345
+ if self.clusterName == "cara":
346
+
347
+ slurm_args = get_default_slurm_args()
348
+ cara_partion = os.environ.get("CARA_PARTITION", slurm_args["partition"])
349
+ slurm_args["partition"] = cara_partion
350
+ if cara_partion == "ppp":
351
+ slurm_args["ntasks"] = "1"
352
+
353
+ for sarg, sval in slurm_args.items():
354
+ cmd = f"#SBATCH --{sarg}"
355
+ cmd += "" if not sval else f"={sval}"
356
+ runScriptCmds.append(cmd)
357
+
358
+ runScriptCmds += [
359
+ ". /usr/share/lmod/lmod/init/sh",
360
+ ". /etc/profile.d/10-dlr-modulepath.sh",
361
+ "module --force purge",
362
+ ]
363
+
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
+ ]
374
+
375
+ runScriptFile = os.path.join(self.runDir, self.REMOTE_SCRIPT)
376
+ with open(runScriptFile, "w") as f:
377
+ f.write("\n".join(runScriptCmds))
378
+
379
+ dos2unix(runScriptFile)
380
+
381
+ def __checkClusterName(self, cname):
382
+ if not re.search("cara|wsl", cname):
383
+ raise NotImplementedError(f"No remote caller implemented for cluster '{cname}'")
384
+
385
+ def _getClusterName(self):
386
+ return self._clusterName
387
+
388
+ def _setClusterName(self, cname):
389
+ self.__checkClusterName(cname)
390
+ self._clusterName = cname
391
+
392
+ clusterName = property(fget=_getClusterName, fset=_setClusterName)
393
+
394
+
395
+ class PythonModuleCaller(ClusterCaller):
396
+ """This class handles calling target functions like those in buckling.bucklingtargetfunction on the cluster"""
397
+
398
+ def __init__(self, runDir, jobCommands):
399
+ """
400
+ :param jobCommands: list with strings defining the command to call a python script excluding the leading python executable
401
+ """
402
+ ClusterCaller.__init__(self, runDir)
403
+ self.solverName = "python35"
404
+ clusterCmds = ["py35", "-d", "-O", "tgz", "-J", "<jobName>"]
405
+ self.remoteCmds = clusterCmds + jobCommands
406
+ self.preCommands = [". ${HOME}/.profile;echo PYTHONPATH:${PYTHONPATH}"]
407
+
408
+
409
+ class MatlabCaller(ClusterCaller):
410
+ """This class handles calling target functions like those in buckling.bucklingtargetfunction on the cluster"""
411
+
412
+ solverName = "matlab"
413
+
414
+ def __init__(self, runDir, jobCommands):
415
+ """
416
+ :param jobCommands: list with strings defining the command to call a matlab script excluding the leading matlabe executable
417
+ """
418
+ ClusterCaller.__init__(self, runDir)
419
+ clusterCmds = ["mat2013a", "-d", "-O", "tgz", "-J", "<jobName>", "--", "-r"]
420
+ self.remoteCmds = clusterCmds + jobCommands