ladim 2.0.3__tar.gz → 2.0.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.
- {ladim-2.0.3 → ladim-2.0.5}/PKG-INFO +2 -2
- {ladim-2.0.3 → ladim-2.0.5}/ladim/__init__.py +1 -1
- {ladim-2.0.3 → ladim-2.0.5}/ladim/__main__.py +1 -1
- {ladim-2.0.3 → ladim-2.0.5}/ladim/config.py +14 -2
- {ladim-2.0.3 → ladim-2.0.5}/ladim/forcing.py +2 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/grid.py +14 -4
- {ladim-2.0.3 → ladim-2.0.5}/ladim/gridforce/zROMS.py +55 -33
- {ladim-2.0.3 → ladim-2.0.5}/ladim/output.py +0 -1
- {ladim-2.0.3 → ladim-2.0.5}/ladim.egg-info/PKG-INFO +2 -2
- {ladim-2.0.3 → ladim-2.0.5}/tests/test_config.py +5 -1
- {ladim-2.0.3 → ladim-2.0.5}/LICENSE +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/README.md +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/gridforce/ROMS.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/gridforce/__init__.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/gridforce/analytical.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/ibms/__init__.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/ibms/light.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/main.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/model.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/plugins/__init__.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/release.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/sample.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/solver.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/state.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/tracker.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim/utilities.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim.egg-info/SOURCES.txt +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim.egg-info/dependency_links.txt +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim.egg-info/entry_points.txt +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim.egg-info/requires.txt +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/ladim.egg-info/top_level.txt +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/postladim/__init__.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/postladim/cellcount.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/postladim/kde_plot.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/postladim/particlefile.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/postladim/variable.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/pyproject.toml +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/setup.cfg +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/tests/test_forcing.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/tests/test_ladim.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/tests/test_model.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/tests/test_output.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/tests/test_release.py +0 -0
- {ladim-2.0.3 → ladim-2.0.5}/tests/test_solver.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
from . import run
|
|
2
|
-
run()
|
|
2
|
+
run()
|
|
@@ -59,15 +59,21 @@ def dict_get_single(d, item):
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
def convert_1_to_2(c):
|
|
62
|
-
|
|
63
62
|
out = {}
|
|
64
63
|
|
|
64
|
+
# If any of the top-level attribute values in `c` are None, they should be
|
|
65
|
+
# converted to empty dicts
|
|
66
|
+
top_level_nones = [k for k in c if c[k] is None]
|
|
67
|
+
c = c.copy()
|
|
68
|
+
for k in top_level_nones:
|
|
69
|
+
c[k] = dict()
|
|
70
|
+
|
|
65
71
|
# Read timedelta
|
|
66
72
|
dt_sec = None
|
|
67
73
|
if 'numerics' in c:
|
|
68
74
|
if 'dt' in c['numerics']:
|
|
69
75
|
dt_value, dt_unit = c['numerics']['dt']
|
|
70
|
-
dt_sec = np.timedelta64(dt_value, dt_unit).astype('timedelta64[s]').astype(
|
|
76
|
+
dt_sec = int(np.timedelta64(dt_value, dt_unit).astype('timedelta64[s]').astype(int))
|
|
71
77
|
|
|
72
78
|
out['version'] = 2
|
|
73
79
|
|
|
@@ -80,13 +86,17 @@ def convert_1_to_2(c):
|
|
|
80
86
|
|
|
81
87
|
out['grid'] = {}
|
|
82
88
|
out['grid']['file'] = dict_get(c, [
|
|
89
|
+
'gridforce.first_file',
|
|
83
90
|
'files.grid_file', 'gridforce.grid_file',
|
|
84
91
|
'files.input_file', 'gridforce.input_file'])
|
|
85
92
|
out['grid']['legacy_module'] = dict_get(c, 'gridforce.module', '') + '.Grid'
|
|
86
93
|
out['grid']['start_time'] = np.datetime64(dict_get(c, 'time_control.start_time', '1970'), 's')
|
|
94
|
+
out['grid']['subgrid'] = dict_get(c, 'gridforce.subgrid', None)
|
|
87
95
|
|
|
88
96
|
out['forcing'] = {}
|
|
89
97
|
out['forcing']['file'] = dict_get(c, ['gridforce.input_file', 'files.input_file'])
|
|
98
|
+
out['forcing']['first_file'] = dict_get(c, 'gridforce.first_file', "")
|
|
99
|
+
out['forcing']['last_file'] = dict_get(c, 'gridforce.last_file', "")
|
|
90
100
|
out['forcing']['legacy_module'] = dict_get(c, 'gridforce.module', '') + '.Forcing'
|
|
91
101
|
out['forcing']['start_time'] = np.datetime64(dict_get(c, 'time_control.start_time', '1970'), 's')
|
|
92
102
|
out['forcing']['stop_time'] = np.datetime64(dict_get(c, 'time_control.stop_time', '1970'), 's')
|
|
@@ -133,6 +143,8 @@ def convert_1_to_2(c):
|
|
|
133
143
|
if 'ibm' in c:
|
|
134
144
|
out['ibm']['module'] = 'ladim.ibms.LegacyIBM'
|
|
135
145
|
out['ibm']['legacy_module'] = dict_get(c, ['ibm.ibm_module', 'ibm.module'])
|
|
146
|
+
if out['ibm']['legacy_module'] == 'ladim.ibms.ibm_salmon_lice':
|
|
147
|
+
out['ibm']['legacy_module'] = 'ladim_plugins.salmon_lice'
|
|
136
148
|
out['ibm']['conf'] = {}
|
|
137
149
|
out['ibm']['conf']['dt'] = dt_sec
|
|
138
150
|
out['ibm']['conf']['output_instance'] = dict_get(c, 'output_variables.instance', [])
|
|
@@ -45,6 +45,8 @@ class RomsForcing(Forcing):
|
|
|
45
45
|
legacy_conf = dict(
|
|
46
46
|
gridforce=dict(
|
|
47
47
|
input_file=file,
|
|
48
|
+
first_file=conf.get('first_file', ""),
|
|
49
|
+
last_file=conf.get('last_file', ""),
|
|
48
50
|
),
|
|
49
51
|
ibm_forcing=conf.get('ibm_forcing', []),
|
|
50
52
|
start_time=conf.get('start_time', None),
|
|
@@ -22,18 +22,28 @@ class Grid(Module):
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class RomsGrid(Grid):
|
|
25
|
-
def __init__(
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
model: Model,
|
|
28
|
+
file: str,
|
|
29
|
+
start_time=None,
|
|
30
|
+
subgrid=None,
|
|
31
|
+
legacy_module='ladim.gridforce.ROMS.Grid',
|
|
32
|
+
**_,
|
|
33
|
+
):
|
|
26
34
|
super().__init__(model)
|
|
27
35
|
|
|
28
36
|
legacy_conf = dict(
|
|
29
37
|
gridforce=dict(
|
|
30
|
-
input_file=
|
|
38
|
+
input_file=file,
|
|
31
39
|
),
|
|
32
|
-
start_time=
|
|
40
|
+
start_time=start_time,
|
|
33
41
|
)
|
|
42
|
+
if subgrid is not None:
|
|
43
|
+
legacy_conf['gridforce']['subgrid'] = subgrid
|
|
34
44
|
|
|
35
45
|
from .model import load_class
|
|
36
|
-
LegacyGrid = load_class(
|
|
46
|
+
LegacyGrid = load_class(legacy_module)
|
|
37
47
|
|
|
38
48
|
# Allow gridforce module in current directory
|
|
39
49
|
import sys
|
|
@@ -15,7 +15,7 @@ import glob
|
|
|
15
15
|
import logging
|
|
16
16
|
import numpy as np
|
|
17
17
|
from netCDF4 import Dataset, num2date
|
|
18
|
-
from ladim.sample import sample2D
|
|
18
|
+
from ladim.sample import sample2D, bilin_inv
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
@@ -107,11 +107,6 @@ class Grid:
|
|
|
107
107
|
self.lon = ncid.variables["lon_rho"][self.J, self.I]
|
|
108
108
|
self.lat = ncid.variables["lat_rho"][self.J, self.I]
|
|
109
109
|
|
|
110
|
-
# self.z_r = sdepth(self.H, self.hc, self.Cs_r,
|
|
111
|
-
# stagger='rho', Vtransform=self.Vtransform)
|
|
112
|
-
# self.z_w = sdepth(self.H, self.hc, self.Cs_w,
|
|
113
|
-
# stagger='w', Vtransform=self.Vtransform)
|
|
114
|
-
|
|
115
110
|
# Land masks at u- and v-points
|
|
116
111
|
M = self.M
|
|
117
112
|
Mu = np.zeros((self.jmax, self.imax + 1), dtype=int)
|
|
@@ -137,6 +132,10 @@ class Grid:
|
|
|
137
132
|
I = X.round().astype(int) - self.i0
|
|
138
133
|
J = Y.round().astype(int) - self.j0
|
|
139
134
|
|
|
135
|
+
# Constrain to valid indices
|
|
136
|
+
I = np.minimum(np.maximum(I, 0), self.dx.shape[-1] - 2)
|
|
137
|
+
J = np.minimum(np.maximum(J, 0), self.dx.shape[-2] - 2)
|
|
138
|
+
|
|
140
139
|
# Metric is conform for PolarStereographic
|
|
141
140
|
A = self.dx[J, I]
|
|
142
141
|
return A, A
|
|
@@ -145,30 +144,39 @@ class Grid:
|
|
|
145
144
|
"""Return the depth of grid cells"""
|
|
146
145
|
I = X.round().astype(int) - self.i0
|
|
147
146
|
J = Y.round().astype(int) - self.j0
|
|
147
|
+
I = np.minimum(np.maximum(I, 0), self.H.shape[1] - 1)
|
|
148
|
+
J = np.minimum(np.maximum(J, 0), self.H.shape[0] - 1)
|
|
148
149
|
return self.H[J, I]
|
|
149
150
|
|
|
150
151
|
def lonlat(self, X, Y, method="bilinear"):
|
|
151
152
|
"""Return the longitude and latitude from grid coordinates"""
|
|
152
153
|
if method == "bilinear": # More accurate
|
|
153
154
|
return self.xy2ll(X, Y)
|
|
154
|
-
else:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
# else: containing grid cell, less accurate
|
|
156
|
+
I = X.round().astype("int") - self.i0
|
|
157
|
+
J = Y.round().astype("int") - self.j0
|
|
158
|
+
I = np.minimum(np.maximum(I, 0), self.lon.shape[1] - 1)
|
|
159
|
+
J = np.minimum(np.maximum(J, 0), self.lon.shape[0] - 1)
|
|
160
|
+
return self.lon[J, I], self.lat[J, I]
|
|
161
|
+
|
|
162
|
+
def ingrid(self, X: np.ndarray, Y: np.ndarray) -> np.ndarray:
|
|
163
|
+
"""Returns True for points inside the subgrid"""
|
|
159
164
|
return (
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
(self.xmin + 0.5 < X)
|
|
166
|
+
& (X < self.xmax - 0.5)
|
|
167
|
+
& (self.ymin + 0.5 < Y)
|
|
168
|
+
& (Y < self.ymax - 0.5)
|
|
162
169
|
)
|
|
163
170
|
|
|
164
|
-
def ingrid(self, X, Y):
|
|
165
|
-
"""Returns True for points inside the subgrid"""
|
|
166
|
-
return (self.xmin < X) & (X < self.xmax) & (self.ymin < Y) & (Y < self.ymax)
|
|
167
|
-
|
|
168
171
|
def onland(self, X, Y):
|
|
169
172
|
"""Returns True for points on land"""
|
|
170
173
|
I = X.round().astype(int) - self.i0
|
|
171
174
|
J = Y.round().astype(int) - self.j0
|
|
175
|
+
|
|
176
|
+
# Constrain to valid indices
|
|
177
|
+
I = np.minimum(np.maximum(I, 0), self.M.shape[-1] - 1)
|
|
178
|
+
J = np.minimum(np.maximum(J, 0), self.M.shape[-2] - 1)
|
|
179
|
+
|
|
172
180
|
return self.M[J, I] < 1
|
|
173
181
|
|
|
174
182
|
# Error if point outside
|
|
@@ -176,6 +184,11 @@ class Grid:
|
|
|
176
184
|
"""Returns True for points at sea"""
|
|
177
185
|
I = X.round().astype(int) - self.i0
|
|
178
186
|
J = Y.round().astype(int) - self.j0
|
|
187
|
+
|
|
188
|
+
# Constrain to valid indices
|
|
189
|
+
I = np.minimum(np.maximum(I, 0), self.M.shape[-1] - 1)
|
|
190
|
+
J = np.minimum(np.maximum(J, 0), self.M.shape[-2] - 1)
|
|
191
|
+
|
|
179
192
|
return self.M[J, I] > 0
|
|
180
193
|
|
|
181
194
|
def xy2ll(self, X, Y):
|
|
@@ -205,7 +218,7 @@ class Forcing:
|
|
|
205
218
|
logger.info("Initiating forcing")
|
|
206
219
|
|
|
207
220
|
self._grid = grid # Get the grid object, make private?
|
|
208
|
-
|
|
221
|
+
# self.config = config["gridforce"]
|
|
209
222
|
self.ibm_forcing = config["ibm_forcing"]
|
|
210
223
|
|
|
211
224
|
# Test for glob, use MFDataset if needed
|
|
@@ -324,6 +337,7 @@ class Forcing:
|
|
|
324
337
|
# --------------
|
|
325
338
|
# prestep = last forcing step < 0
|
|
326
339
|
#
|
|
340
|
+
|
|
327
341
|
V = [step for step in steps if step < 0]
|
|
328
342
|
if V: # Forcing available before start time
|
|
329
343
|
prestep = max(V)
|
|
@@ -345,11 +359,12 @@ class Forcing:
|
|
|
345
359
|
|
|
346
360
|
elif steps[0] == 0:
|
|
347
361
|
# Simulation start at first forcing time
|
|
362
|
+
# Runge-Kutta needs dU and dV in this case as well
|
|
348
363
|
self.U, self.V = self._read_velocity(0)
|
|
349
364
|
self.Unew, self.Vnew = self._read_velocity(steps[1])
|
|
350
365
|
self.dU = (self.Unew - self.U) / steps[1]
|
|
351
366
|
self.dV = (self.Vnew - self.V) / steps[1]
|
|
352
|
-
#
|
|
367
|
+
# Synchronize with start time
|
|
353
368
|
self.Unew = self.U
|
|
354
369
|
self.Vnew = self.V
|
|
355
370
|
# Extrapolate to time step = -1
|
|
@@ -372,7 +387,7 @@ class Forcing:
|
|
|
372
387
|
def update(self, t):
|
|
373
388
|
"""Update the fields to time step t"""
|
|
374
389
|
|
|
375
|
-
# Read from config
|
|
390
|
+
# Read from config?
|
|
376
391
|
interpolate_velocity_in_time = True
|
|
377
392
|
interpolate_ibm_forcing_in_time = False
|
|
378
393
|
|
|
@@ -413,7 +428,7 @@ class Forcing:
|
|
|
413
428
|
|
|
414
429
|
# Handle file opening/closing
|
|
415
430
|
# Always read velocity before other fields
|
|
416
|
-
logger.
|
|
431
|
+
logger.info("Reading velocity for time step = {}".format(n))
|
|
417
432
|
first = True
|
|
418
433
|
if first: # Open file initiallt
|
|
419
434
|
self._nc = Dataset(self._files[self.file_idx[n]])
|
|
@@ -430,6 +445,7 @@ class Forcing:
|
|
|
430
445
|
# Read the velocity
|
|
431
446
|
U = self._nc.variables["u"][frame, :, self._grid.Ju, self._grid.Iu]
|
|
432
447
|
V = self._nc.variables["v"][frame, :, self._grid.Jv, self._grid.Iv]
|
|
448
|
+
|
|
433
449
|
# Scale if needed
|
|
434
450
|
# Assume offset = 0 for velocity
|
|
435
451
|
if self.scaled["U"]:
|
|
@@ -446,7 +462,6 @@ class Forcing:
|
|
|
446
462
|
|
|
447
463
|
def _read_field(self, name, n):
|
|
448
464
|
"""Read a 3D field"""
|
|
449
|
-
# print("IBM-forcing:", name)
|
|
450
465
|
frame = self.frame_idx[n]
|
|
451
466
|
F = self._nc.variables[name][frame, :, self._grid.J, self._grid.I]
|
|
452
467
|
if self.scaled[name]:
|
|
@@ -579,7 +594,7 @@ def sdepth(H, Hc, C, stagger="rho", Vtransform=1):
|
|
|
579
594
|
"""
|
|
580
595
|
H = np.asarray(H)
|
|
581
596
|
Hshape = H.shape # Save the shape of H
|
|
582
|
-
H = H.ravel() # and make H 1D for easy shape
|
|
597
|
+
H = H.ravel() # and make H 1D for easy shape maniplation
|
|
583
598
|
C = np.asarray(C)
|
|
584
599
|
N = len(C)
|
|
585
600
|
outshape = (N,) + Hshape # Shape of output
|
|
@@ -595,13 +610,13 @@ def sdepth(H, Hc, C, stagger="rho", Vtransform=1):
|
|
|
595
610
|
B = np.outer(C, H)
|
|
596
611
|
return (A + B).reshape(outshape)
|
|
597
612
|
|
|
598
|
-
|
|
613
|
+
if Vtransform == 2: # New transform by Shchepetkin
|
|
599
614
|
N = Hc * S[:, None] + np.outer(C, H)
|
|
600
615
|
D = 1.0 + Hc / H
|
|
601
616
|
return (N / D).reshape(outshape)
|
|
602
617
|
|
|
603
|
-
else:
|
|
604
|
-
|
|
618
|
+
# else:
|
|
619
|
+
raise ValueError("Unknown Vtransform")
|
|
605
620
|
|
|
606
621
|
|
|
607
622
|
# ------------------------
|
|
@@ -660,13 +675,15 @@ def sample3D(F, X, Y, K, A, method="bilinear"):
|
|
|
660
675
|
|
|
661
676
|
"""
|
|
662
677
|
|
|
663
|
-
# print('sample3D: method =', method)
|
|
664
|
-
|
|
665
|
-
# sjekk om 1-A eller A
|
|
666
678
|
if method == "bilinear":
|
|
667
679
|
# Find rho-point as lower left corner
|
|
668
680
|
I = X.astype("int")
|
|
669
681
|
J = Y.astype("int")
|
|
682
|
+
|
|
683
|
+
# Constrain to valid indices
|
|
684
|
+
I = np.minimum(np.maximum(I, 0), F.shape[-1] - 2)
|
|
685
|
+
J = np.minimum(np.maximum(J, 0), F.shape[-2] - 2)
|
|
686
|
+
|
|
670
687
|
P = X - I
|
|
671
688
|
Q = Y - J
|
|
672
689
|
W000 = (1 - P) * (1 - Q) * A
|
|
@@ -689,10 +706,15 @@ def sample3D(F, X, Y, K, A, method="bilinear"):
|
|
|
689
706
|
+ W111 * F[K - 1, J + 1, I + 1]
|
|
690
707
|
)
|
|
691
708
|
|
|
692
|
-
else:
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
709
|
+
# else: method == 'nearest'
|
|
710
|
+
I = X.round().astype("int")
|
|
711
|
+
J = Y.round().astype("int")
|
|
712
|
+
|
|
713
|
+
# Constrain to valid indices
|
|
714
|
+
I = np.minimum(np.maximum(I, 0), F.shape[-1] - 1)
|
|
715
|
+
J = np.minimum(np.maximum(J, 0), F.shape[-2] - 1)
|
|
716
|
+
|
|
717
|
+
return F[K, J, I]
|
|
696
718
|
|
|
697
719
|
|
|
698
720
|
def sample3DUV(U, V, X, Y, K, A, method="bilinear"):
|
|
@@ -37,6 +37,8 @@ class Test_convert_1_to_2:
|
|
|
37
37
|
'forcing': {
|
|
38
38
|
'dt': 60,
|
|
39
39
|
'file': '../forcing*.nc',
|
|
40
|
+
'first_file': '',
|
|
41
|
+
'last_file': '',
|
|
40
42
|
'ibm_forcing': [],
|
|
41
43
|
'legacy_module': 'ladim.gridforce.ROMS.Forcing',
|
|
42
44
|
'start_time': np.datetime64('2015-09-07T01:00:00'),
|
|
@@ -44,7 +46,9 @@ class Test_convert_1_to_2:
|
|
|
44
46
|
'grid': {
|
|
45
47
|
'file': '../forcing*.nc',
|
|
46
48
|
'legacy_module': 'ladim.gridforce.ROMS.Grid',
|
|
47
|
-
'start_time': np.datetime64('2015-09-07T01:00:00')
|
|
49
|
+
'start_time': np.datetime64('2015-09-07T01:00:00'),
|
|
50
|
+
'subgrid': None,
|
|
51
|
+
},
|
|
48
52
|
'ibm': {},
|
|
49
53
|
'output': {
|
|
50
54
|
'file': 'out.nc',
|
|
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
|
|
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
|
|
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
|
|
File without changes
|