tilupy 2.0.0__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.
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import os
5
+ import posixpath
6
+ import numpy as np
7
+
8
+ from tilupy.utils import format_path_linux
9
+
10
+ import tilupy.notations
11
+ import tilupy.raster
12
+
13
+
14
+ README_PARAM_MATCH = dict(tmax="tmax", CFL="cflhyp", h_min="eps0", dt_im_output="dt_im")
15
+ """Dictionary of correspondence between parameters in read_me file and param file."""
16
+
17
+ SHALTOP_LAW_ID = dict(No_Friction=1, Herschel_Bulkley=61, Voellmy=8, Bingham=6, Coulomb=1, Coulomb_muI=7)
18
+ """Dictionary of correspondence between the name of rheological laws and the corresponding ID in SHALTOP.
19
+
20
+ Correspondence :
21
+
22
+ - No_Friction = 1
23
+ - Coulomb = 1
24
+ - Coulomb_muI = 7
25
+ - Voellmy = 8
26
+ - Bingham = 6
27
+ - Herschel_Bulkley = 61
28
+ """
29
+
30
+
31
+ def raster_to_shaltop_txtfile(file_in: str,
32
+ file_out: str,
33
+ folder_out: str=None
34
+ ) -> dict:
35
+ """Convert a raster file to a Shaltop ASCII text format.
36
+
37
+ Reads a raster (formats readable by :func:`tilupy.raster.read_raster`) and saves it as a
38
+ ASCII text file with values flattened column-wise and rows flipped vertically.
39
+
40
+ Parameters
41
+ ----------
42
+ file_in : str
43
+ Path to the input raster file.
44
+ file_out : str
45
+ Name of the output ASCII text file.
46
+ folder_out : str, optional
47
+ Directory where the output file will be saved. If None, `file_out`
48
+ is used as-is, by default None.
49
+
50
+ Returns
51
+ -------
52
+ dict
53
+ Dictionary containing grid metadata:
54
+
55
+ - 'x0': X coordinate of the first column.
56
+ - 'y0': Y coordinate of the first row.
57
+ - 'dx': Grid spacing along X.
58
+ - 'dy': Grid spacing along Y.
59
+ - 'nx': Number of columns.
60
+ - 'ny': Number of rows.
61
+ """
62
+ if folder_out is not None:
63
+ file_out = os.path.join(folder_out, file_out)
64
+
65
+ x, y, rast = tilupy.raster.read_raster(file_in)
66
+ np.savetxt(file_out,
67
+ np.reshape(np.flip(rast, axis=0), (rast.size, 1)),
68
+ fmt="%.12G")
69
+
70
+ res = dict(x0=x[0],
71
+ y0=y[0],
72
+ dx=x[1] - x[0],
73
+ dy=y[1] - y[0],
74
+ nx=len(x),
75
+ ny=len(y))
76
+
77
+ return res
78
+
79
+
80
+ def write_params_file(params: dict,
81
+ directory: str=None,
82
+ file_name: str="params.txt"
83
+ ) -> None:
84
+ """Write a dictionary of parameters to a text file.
85
+
86
+ Each key-value pair in the dictionary is written on a separate line.
87
+
88
+ Parameters
89
+ ----------
90
+ params : dict
91
+ Dictionary of parameter names and values. Values can be int, float,
92
+ or str.
93
+ directory : str, optional
94
+ Directory where the parameter file will be written. Default is the
95
+ current working directory.
96
+ file_name : str, optional
97
+ Name of the parameter file. Default is "params.txt".
98
+ """
99
+ if directory is None:
100
+ directory = os.getcwd()
101
+
102
+ with open(os.path.join(directory, file_name), "w") as file_params:
103
+ for name in params:
104
+ val = params[name]
105
+ if (isinstance(val, int)
106
+ or isinstance(val, np.int64)
107
+ or isinstance(val, np.int32)
108
+ ):
109
+ file_params.write("{:s} {:d}\n".format(name, val))
110
+
111
+ if (isinstance(val, float)
112
+ or isinstance(val, np.float64)
113
+ or isinstance(val, np.float32)
114
+ ):
115
+ file_params.write("{:s} {:.8G}\n".format(name, val))
116
+
117
+ if isinstance(val, str):
118
+ file_params.write("{:s} {:s}\n".format(name, val))
119
+
120
+
121
+ def write_simu(raster_topo: str,
122
+ raster_mass: str,
123
+ tmax : float,
124
+ dt_im : float,
125
+ rheology_type: str,
126
+ rheology_params: dict,
127
+ folder_out: str=None,
128
+ ) -> None:
129
+ """
130
+ Prepares the input files required for a SHALTOP simulation and saves them in a dedicated folder.
131
+
132
+ Parameters
133
+ ----------
134
+ raster_topo : str
135
+ Name of the ASCII topography file.
136
+ raster_mass : str
137
+ Name of the ASCII initial mass file.
138
+ tmax : float
139
+ Maximum simulation time.
140
+ dt_im : float
141
+ Output image interval (in time steps).
142
+ rheology_type : str
143
+ Rheology to use for the simulation.
144
+ rheology_params : dict
145
+ Necessary parameters for the rheology. For instance:
146
+
147
+ - delta1
148
+ - ksi
149
+ - tau_density
150
+ etc.
151
+ folder_out : str, optional
152
+ Output folder where simulation inputs will be saved.
153
+
154
+ Raises
155
+ ------
156
+ ValueError
157
+ If the rheology is wrong.
158
+ """
159
+ if folder_out is None:
160
+ folder_out = "."
161
+
162
+ # output_file = os.path.join(folder_out, "shaltop")
163
+
164
+ os.makedirs(folder_out, exist_ok=True)
165
+
166
+ x, y, z = tilupy.raster.read_raster(raster_topo)
167
+ raster_to_shaltop_txtfile(raster_topo, os.path.join(folder_out, "z.d"))
168
+ raster_to_shaltop_txtfile(raster_mass, os.path.join(folder_out, "m.d"))
169
+ folder_output = "data2"
170
+ os.makedirs(os.path.join(folder_out, folder_output), exist_ok=True)
171
+
172
+ if rheology_type not in SHALTOP_LAW_ID:
173
+ raise ValueError(f"Wrong law, choose in: {SHALTOP_LAW_ID}")
174
+
175
+ params = dict(nx=len(x),
176
+ ny=len(y),
177
+ per=x[-1],
178
+ pery=y[-1],
179
+ tmax=tmax,
180
+ dt_im=dt_im,
181
+ initz=0,
182
+ file_z_init="z.d",
183
+ ipr=0,
184
+ file_m_init="m.d",
185
+ folder_output="data2",
186
+ icomp=SHALTOP_LAW_ID[rheology_type],
187
+ **rheology_params)
188
+ write_params_file(params, directory=folder_out, file_name="params.txt")
189
+
190
+
191
+ def write_job_files(dirs: list[str],
192
+ param_files: list[str],
193
+ file_job: str,
194
+ job_name: str,
195
+ max_time_hours: int=24,
196
+ ncores_per_node: int=6,
197
+ partitions: str="cpuall,data,datanew",
198
+ shaltop_file: str="shaltop",
199
+ folder_conf_in_job: str=None,
200
+ replace_path: list=None,
201
+ number_conf_file: bool=True,
202
+ ) -> None:
203
+ """
204
+ Write job/conf files for slurm jobs. The conf contains all the commands
205
+ needed to run each simulation (one command per simulation).
206
+
207
+ Parameters
208
+ ----------
209
+ dirs : list[str]
210
+ list of paths where simus will be run.
211
+ param_files : list[str]
212
+ list of shaltop parameter files.
213
+ file_job : str
214
+ name of job file called by sbatch.
215
+ job_name : str
216
+ name of conf file used by file_job.
217
+ max_time_hours : int, optional
218
+ Maximum job duration in hours before stop. The default is 24.
219
+ ncores_per_node : int, optional
220
+ Number of cores per nodes. Used to know the number of nodes required
221
+ for the job. The default is 6.
222
+ partitions : str, optional
223
+ Names of partitions on which jobs can be launched.
224
+ The default is "cpuall,data,datanew".
225
+ shaltop_file : str, optional
226
+ Bash command used to call shaltop. Can be a path.
227
+ The default is "shaltop".
228
+ folder_conf_in_job : str, optional
229
+ Folder where the conf file is located. The default is the folder
230
+ path of file_job.
231
+ replace_path : list, optional
232
+ replace replace_path[0] by replace_path[1] for every path in dir. This
233
+ is used if simulations are prepared and run on two different machines
234
+ (e.g. laptop and cluster).
235
+ The default is None.
236
+ number_conf_file : bool, optional
237
+ If True, add a number in front of each line of the conf file. Required
238
+ to identify slurm jobs.
239
+ The default is True.
240
+ """
241
+ ntasks = len(dirs)
242
+ nnodes = int(np.ceil(ntasks / ncores_per_node))
243
+
244
+ if folder_conf_in_job is None:
245
+ folder_conf_in_job = os.path.dirname(file_job)
246
+ if folder_conf_in_job == "":
247
+ folder_conf_in_job = "."
248
+
249
+ with open(file_job + ".conf", "w", newline="\n") as conf_file:
250
+ if number_conf_file:
251
+ line = "{:d} {:s} {:s} {:s}\n"
252
+ else:
253
+ line = "{:s} {:s} {:s}\n"
254
+ for i in range(ntasks):
255
+ if replace_path is not None:
256
+ folder = dirs[i].replace(replace_path[0], replace_path[1])
257
+ param_file = param_files[i].replace(
258
+ replace_path[0], replace_path[1]
259
+ )
260
+ else:
261
+ folder = dirs[i]
262
+ param_file = param_files[i]
263
+ folder = format_path_linux(folder)
264
+ param_file = format_path_linux(param_file)
265
+ if number_conf_file:
266
+ line2 = line.format(i, shaltop_file, folder, param_file)
267
+ else:
268
+ line2 = line.format(shaltop_file, folder, param_file)
269
+ conf_file.write(line2)
270
+
271
+ n_hours = np.floor(max_time_hours)
272
+ n_min = (max_time_hours - n_hours) * 60
273
+ str_time = "{:02.0f}:{:02.0f}:00\n".format(n_hours, n_min)
274
+
275
+ basename = os.path.basename(file_job)
276
+ path_conf_in_job = posixpath.join(folder_conf_in_job, basename + ".conf")
277
+
278
+ with open(file_job + ".job", "w", newline="\n") as job_file:
279
+ job_file.write("#!/bin/sh\n")
280
+ job_file.write("#SBATCH -J multijob\n")
281
+ job_file.write("#SBATCH --job-name={:s}\n".format(job_name))
282
+ job_file.write("#SBATCH --output={:s}%j.out\n".format(job_name))
283
+ job_file.write("#SBATCH --partition " + partitions + "\n")
284
+ job_file.write("#SBATCH --nodes={:d}".format(nnodes) + "\n")
285
+ job_file.write("#SBATCH --ntasks={:d}".format(ntasks) + "\n")
286
+ job_file.write("#SBATCH --time={:s}\n".format(str_time))
287
+ job_file.write("\n")
288
+ job_file.write("module purge\n")
289
+ job_file.write("module load slurm\n")
290
+ job_file.write("\n")
291
+ line = "srun -n {:d} -l --multi-prog {:s}"
292
+ job_file.write(line.format(ntasks, path_conf_in_job))
293
+
294
+
295
+ def make_simus(law: str,
296
+ rheol_params: dict,
297
+ folder_data: str,
298
+ folder_out: str,
299
+ readme_file: str
300
+ ) -> None:
301
+ """Write shaltop initial file for simple slope test case
302
+
303
+ Reads topography and initial mass files in ASCII format,
304
+ writes them in Shaltop-compatible format, prepares simulation parameters
305
+ based on a README file and user-provided values,
306
+ and generates a shell script to run the simulations.
307
+
308
+ Parameters
309
+ ----------
310
+ law : str
311
+ Name of the rheological law to use (must match a key in :data:`SHALTOP_LAW_ID`).
312
+ rheol_params : dict of list
313
+ Dictionary of rheology parameters. Each key corresponds to a parameter
314
+ name and its value is a list of parameter values to simulate.
315
+ folder_data : str
316
+ Path to the folder containing input data files "topo.asc" and "mass.asc".
317
+ folder_out : str
318
+ Path to the folder where output simulation folders and Shaltop files
319
+ will be created.
320
+ readme_file : str
321
+ Path to the README file containing simulation parameters and metadata.
322
+ """
323
+ # Get topography and initial mass, and write them in Shaltop format
324
+ zfile = os.path.join(folder_data, "topo.asc")
325
+ mfile = os.path.join(folder_data, "mass.asc")
326
+ x, y, z, dx = tilupy.raster.read_ascii(zfile)
327
+ _, _, m, _ = tilupy.raster.read_ascii(mfile)
328
+ np.savetxt(os.path.join(folder_out, "z.d"), z.T.flatten())
329
+ np.savetxt(os.path.join(folder_out, "m.d"), m.T.flatten())
330
+
331
+ # Get simulation parameters from README.txt and raster .asc files
332
+ params = tilupy.notations.readme_to_params(readme_file, README_PARAM_MATCH)
333
+ params["nx"] = len(x)
334
+ params["ny"] = len(y)
335
+ params["per"] = dx * len(x)
336
+ params["pery"] = dx * len(y)
337
+ params["file_m_init"] = "../m.d"
338
+ params["file_z_init"] = "../z.d"
339
+
340
+ # Folder for rheological law, and set params accordingly
341
+ folder_law = os.path.join(folder_out, law)
342
+ params["icomp"] = SHALTOP_LAW_ID[law]
343
+
344
+ param_names = [param for param in rheol_params]
345
+
346
+ texts = tilupy.notations.make_rheol_string(rheol_params, law)
347
+
348
+ # Run shaltop file
349
+ run_shaltop_file = os.path.join(folder_law, "run_shaltop.sh")
350
+ file_txt = ""
351
+
352
+ for i in range(len(rheol_params[param_names[0]])):
353
+ simu_text = texts[i]
354
+ for param_name in param_names:
355
+ params[param_name] = rheol_params[param_name][i]
356
+ params["folder_output"] = simu_text
357
+ folder_results = os.path.join(folder_law, simu_text)
358
+ os.makedirs(folder_results, exist_ok=True)
359
+ with open(os.path.join(folder_results, ".gitignore"), "w") as fid:
360
+ fid.write("# Ignore everything in this directory")
361
+ fid.write("*")
362
+ fid.write("# Except this file")
363
+ fid.write("!.gitignore")
364
+
365
+ write_params_file(params, directory=folder_law, file_name=simu_text + ".txt")
366
+ file_txt += "start_time=`date +%s`\n"
367
+ file_txt += 'shaltop "" ' + simu_text + ".txt\n"
368
+ file_txt += "end_time=`date +%s`\n"
369
+ file_txt += "elapsed_time=$(($end_time - $start_time))\n"
370
+ file_txt += ('string_time="${start_time} ' + simu_text + ' ${elapsed_time}"\n')
371
+ file_txt += "echo ${string_time} >> simulation_duration.txt\n\n"
372
+
373
+ with open(run_shaltop_file, "w") as fid:
374
+ fid.write(file_txt)
375
+