ladim 2.0.2__py3-none-any.whl → 2.0.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.
ladim/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
- __version__ = '2.0.2'
1
+ __version__ = '2.0.4'
2
2
 
3
3
  from .main import main, run
ladim/config.py CHANGED
@@ -28,98 +28,126 @@ def configure(module_conf):
28
28
 
29
29
  def _versioned_configure(config_dict):
30
30
  if config_dict['version'] == 1:
31
- config_dict = _convert_1_to_2(config_dict)
31
+ config_dict = convert_1_to_2(config_dict)
32
32
 
33
33
  return config_dict
34
34
 
35
35
 
36
- def _convert_1_to_2(c):
36
+ def dict_get(d, items, default=None):
37
+ if isinstance(items, str):
38
+ items = [items]
39
+
40
+ for item in items:
41
+ try:
42
+ return dict_get_single(d, item)
43
+ except KeyError:
44
+ pass
45
+
46
+ return default
47
+
48
+
49
+ def dict_get_single(d, item):
50
+ tokens = str(item).split(sep='.')
51
+ sub_dict = d
52
+ for t in tokens:
53
+ if t in sub_dict:
54
+ sub_dict = sub_dict[t]
55
+ else:
56
+ raise KeyError
57
+
58
+ return sub_dict
59
+
60
+
61
+ def convert_1_to_2(c):
62
+ out = {}
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
+
37
71
  # Read timedelta
38
- dt_value, dt_unit = c['numerics']['dt']
39
- dt_sec = np.timedelta64(dt_value, dt_unit).astype('timedelta64[s]').astype('int64')
72
+ dt_sec = None
73
+ if 'numerics' in c:
74
+ if 'dt' in c['numerics']:
75
+ dt_value, dt_unit = c['numerics']['dt']
76
+ dt_sec = np.timedelta64(dt_value, dt_unit).astype('timedelta64[s]').astype('int64')
77
+
78
+ out['version'] = 2
79
+
80
+ out['solver'] = {}
81
+ out['solver']['start'] = dict_get(c, 'time_control.start_time')
82
+ out['solver']['stop'] = dict_get(c, 'time_control.stop_time')
83
+ out['solver']['step'] = dt_sec
84
+ out['solver']['seed'] = dict_get(c, 'numerics.seed')
85
+ out['solver']['order'] = ['release', 'forcing', 'output', 'tracker', 'ibm', 'state']
86
+
87
+ out['grid'] = {}
88
+ out['grid']['file'] = dict_get(c, [
89
+ 'files.grid_file', 'gridforce.grid_file',
90
+ 'files.input_file', 'gridforce.input_file'])
91
+ out['grid']['legacy_module'] = dict_get(c, 'gridforce.module', '') + '.Grid'
92
+ out['grid']['start_time'] = np.datetime64(dict_get(c, 'time_control.start_time', '1970'), 's')
93
+
94
+ out['forcing'] = {}
95
+ out['forcing']['file'] = dict_get(c, ['gridforce.input_file', 'files.input_file'])
96
+ out['forcing']['legacy_module'] = dict_get(c, 'gridforce.module', '') + '.Forcing'
97
+ out['forcing']['start_time'] = np.datetime64(dict_get(c, 'time_control.start_time', '1970'), 's')
98
+ out['forcing']['stop_time'] = np.datetime64(dict_get(c, 'time_control.stop_time', '1970'), 's')
99
+ out['forcing']['dt'] = dt_sec
100
+ out['forcing']['ibm_forcing'] = dict_get(c, 'gridforce.ibm_forcing', [])
40
101
 
41
- # Read output variables
42
- outvars = dict()
43
- outvar_names = c['output_variables'].get('particle', []) + c['output_variables'].get('instance', [])
102
+ out['output'] = {}
103
+ out['output']['file'] = dict_get(c, 'files.output_file')
104
+ out['output']['frequency'] = dict_get(c, 'output_variables.outper')
105
+ out['output']['variables'] = {}
106
+
107
+ # Convert output variable format spec
108
+ outvar_names = dict_get(c, 'output_variables.particle', []).copy()
109
+ outvar_names += dict_get(c, 'output_variables.instance', [])
44
110
  for v in outvar_names:
45
- outvars[v] = c['output_variables'][v].copy()
46
- for v in c['output_variables'].get('particle', []):
47
- outvars[v]['kind'] = 'initial'
48
- if 'release_time' in outvars and 'units' in outvars['release_time']:
49
- outvars['release_time']['units'] = 'seconds since 1970-01-01'
111
+ out['output']['variables'][v] = c['output_variables'][v].copy()
112
+ if v == 'release_time' and 'units' in c['output_variables'][v]:
113
+ out['output']['variables'][v]['units'] = 'seconds since 1970-01-01'
114
+ for v in dict_get(c, 'output_variables.particle', []):
115
+ out['output']['variables'][v]['kind'] = 'initial'
116
+
117
+ out['tracker'] = {}
118
+ out['tracker']['method'] = dict_get(c, 'numerics.advection')
119
+ out['tracker']['diffusion'] = dict_get(c, 'numerics.diffusion')
50
120
 
51
121
  # Read release config
52
- relconf = dict(
53
- file=c['files']['particle_release_file'],
54
- frequency=c['particle_release'].get('release_frequency', [0, 's']),
55
- colnames=c['particle_release']['variables'],
56
- formats={
57
- c['particle_release'][v]: v
58
- for v in c['particle_release']['variables']
59
- if v in c['particle_release'].keys()
60
- },
61
- )
62
- if c['particle_release'].get('release_type', '') != 'continuous':
63
- del relconf['frequency']
64
- ibmvars = c.get('state', dict()).get('ibm_variables', [])
65
- ibmvars += c.get('ibm', dict()).get('variables', [])
66
- relconf['defaults'] = {
122
+ out['release'] = {}
123
+ out['release']['file'] = dict_get(c, 'files.particle_release_file')
124
+ out['release']['colnames'] = dict_get(c, 'particle_release.variables', [])
125
+ if dict_get(c, 'particle_release.release_type', '') == 'continuous':
126
+ out['release']['frequency'] = dict_get(c, 'particle_release.release_frequency', [0, 's'])
127
+ out['release']['formats'] = {
128
+ c.get('particle_release', {})[v]: v
129
+ for v in dict_get(c, 'particle_release.variables', [])
130
+ if v in c.get('particle_release', {}).keys()
131
+ }
132
+ out['release']['defaults'] = {
67
133
  k: np.float64(0)
68
- for k in ibmvars
69
- if k not in relconf['colnames']
134
+ for k in dict_get(c, 'state.ibm_variables', []) + dict_get(c, 'ibm.variables', [])
135
+ if k not in out['release']['colnames']
70
136
  }
71
137
 
72
- # Read ibm config
73
- ibmconf_legacy = c.get('ibm', dict()).copy()
74
- if 'module' in ibmconf_legacy:
75
- ibmconf_legacy['ibm_module'] = ibmconf_legacy.pop('module')
76
- ibmconf = dict()
77
- if 'ibm_module' in ibmconf_legacy:
78
- ibmconf['module'] = 'ladim.ibms.LegacyIBM'
79
- ibmconf['legacy_module'] = ibmconf_legacy['ibm_module']
80
- ibmconf['conf'] = dict(
81
- dt=dt_sec,
82
- output_instance=c.get('output_variables', {}).get('instance', []),
83
- nc_attributes={k: v for k, v in outvars.items()}
84
- )
85
- ibmconf['conf']['ibm'] = {
138
+ out['ibm'] = {}
139
+ if 'ibm' in c:
140
+ out['ibm']['module'] = 'ladim.ibms.LegacyIBM'
141
+ out['ibm']['legacy_module'] = dict_get(c, ['ibm.ibm_module', 'ibm.module'])
142
+ if out['ibm']['legacy_module'] == 'ladim.ibms.ibm_salmon_lice':
143
+ out['ibm']['legacy_module'] = 'ladim_plugins.salmon_lice'
144
+ out['ibm']['conf'] = {}
145
+ out['ibm']['conf']['dt'] = dt_sec
146
+ out['ibm']['conf']['output_instance'] = dict_get(c, 'output_variables.instance', [])
147
+ out['ibm']['conf']['nc_attributes'] = {
86
148
  k: v
87
- for k, v in ibmconf_legacy.items()
88
- if k != 'ibm_module'
149
+ for k, v in out['output']['variables'].items()
89
150
  }
151
+ out['ibm']['conf']['ibm'] = {k: v for k, v in c['ibm'].items() if k != 'ibm_module'}
90
152
 
91
- config_dict = dict(
92
- version=2,
93
- solver=dict(
94
- start=c['time_control']['start_time'],
95
- stop=c['time_control']['stop_time'],
96
- step=dt_sec,
97
- seed=c['numerics'].get('seed', None),
98
- order=['release', 'forcing', 'output', 'tracker', 'ibm', 'state'],
99
- ),
100
- grid=dict(
101
- file=c['gridforce']['input_file'],
102
- legacy_module=c['gridforce']['module'] + '.Grid',
103
- start_time=np.datetime64(c['time_control']['start_time'], 's'),
104
- ),
105
- forcing=dict(
106
- file=c['gridforce']['input_file'],
107
- legacy_module=c['gridforce']['module'] + '.Forcing',
108
- start_time=np.datetime64(c['time_control']['start_time'], 's'),
109
- stop_time=np.datetime64(c['time_control']['stop_time'], 's'),
110
- dt=dt_sec,
111
- ibm_forcing=c['gridforce'].get('ibm_forcing', []),
112
- ),
113
- release=relconf,
114
- output=dict(
115
- file=c['files']['output_file'],
116
- frequency=c['output_variables']['outper'],
117
- variables=outvars,
118
- ),
119
- tracker=dict(
120
- method=c['numerics']['advection'],
121
- diffusion=c['numerics']['diffusion'],
122
- ),
123
- ibm=ibmconf,
124
- )
125
- return config_dict
153
+ return out
ladim/gridforce/ROMS.py CHANGED
@@ -182,6 +182,8 @@ class Grid:
182
182
  """Return the depth of grid cells"""
183
183
  I = X.round().astype(int) - self.i0
184
184
  J = Y.round().astype(int) - self.j0
185
+ I = np.minimum(np.maximum(I, 0), self.H.shape[1] - 1)
186
+ J = np.minimum(np.maximum(J, 0), self.H.shape[0] - 1)
185
187
  return self.H[J, I]
186
188
 
187
189
  def lonlat(self, X, Y, method="bilinear"):
@@ -191,6 +193,8 @@ class Grid:
191
193
  # else: containing grid cell, less accurate
192
194
  I = X.round().astype("int") - self.i0
193
195
  J = Y.round().astype("int") - self.j0
196
+ I = np.minimum(np.maximum(I, 0), self.lon.shape[1] - 1)
197
+ J = np.minimum(np.maximum(J, 0), self.lon.shape[0] - 1)
194
198
  return self.lon[J, I], self.lat[J, I]
195
199
 
196
200
  def ingrid(self, X: np.ndarray, Y: np.ndarray) -> np.ndarray:
@@ -206,6 +210,11 @@ class Grid:
206
210
  """Returns True for points on land"""
207
211
  I = X.round().astype(int) - self.i0
208
212
  J = Y.round().astype(int) - self.j0
213
+
214
+ # Constrain to valid indices
215
+ I = np.minimum(np.maximum(I, 0), self.M.shape[-1] - 1)
216
+ J = np.minimum(np.maximum(J, 0), self.M.shape[-2] - 1)
217
+
209
218
  return self.M[J, I] < 1
210
219
 
211
220
  # Error if point outside
@@ -802,6 +811,11 @@ def sample3D(F, X, Y, K, A, method="bilinear"):
802
811
  # else: method == 'nearest'
803
812
  I = X.round().astype("int")
804
813
  J = Y.round().astype("int")
814
+
815
+ # Constrain to valid indices
816
+ I = np.minimum(np.maximum(I, 0), F.shape[-1] - 1)
817
+ J = np.minimum(np.maximum(J, 0), F.shape[-2] - 1)
818
+
805
819
  return F[K, J, I]
806
820
 
807
821
 
ladim/gridforce/zROMS.py CHANGED
@@ -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__)
@@ -37,12 +37,23 @@ class Grid:
37
37
  def __init__(self, config):
38
38
 
39
39
  logger.info("Initializing zROMS grid object")
40
+
41
+ # Grid file
42
+ if "grid_file" in config["gridforce"]:
43
+ grid_file = config["gridforce"]["grid_file"]
44
+ elif "input_file" in config["gridforce"]:
45
+ files = glob.glob(config["gridforce"]["input_file"])
46
+ files.sort()
47
+ grid_file = files[0]
48
+ else:
49
+ logger.error("No grid file specified")
50
+ raise SystemExit(1)
51
+
40
52
  try:
41
- ncid = Dataset(config["gridforce"]["grid_file"])
53
+ ncid = Dataset(grid_file)
54
+ ncid.set_auto_mask(False)
42
55
  except OSError:
43
- logger.error(
44
- "Grid file {} not found".format(config["gridforce"]["grid_file"])
45
- )
56
+ logger.error("Could not open grid file " + grid_file)
46
57
  raise SystemExit(1)
47
58
 
48
59
  # Subgrid, only considers internal grid cells
@@ -96,11 +107,6 @@ class Grid:
96
107
  self.lon = ncid.variables["lon_rho"][self.J, self.I]
97
108
  self.lat = ncid.variables["lat_rho"][self.J, self.I]
98
109
 
99
- # self.z_r = sdepth(self.H, self.hc, self.Cs_r,
100
- # stagger='rho', Vtransform=self.Vtransform)
101
- # self.z_w = sdepth(self.H, self.hc, self.Cs_w,
102
- # stagger='w', Vtransform=self.Vtransform)
103
-
104
110
  # Land masks at u- and v-points
105
111
  M = self.M
106
112
  Mu = np.zeros((self.jmax, self.imax + 1), dtype=int)
@@ -126,6 +132,10 @@ class Grid:
126
132
  I = X.round().astype(int) - self.i0
127
133
  J = Y.round().astype(int) - self.j0
128
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
+
129
139
  # Metric is conform for PolarStereographic
130
140
  A = self.dx[J, I]
131
141
  return A, A
@@ -134,30 +144,39 @@ class Grid:
134
144
  """Return the depth of grid cells"""
135
145
  I = X.round().astype(int) - self.i0
136
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)
137
149
  return self.H[J, I]
138
150
 
139
151
  def lonlat(self, X, Y, method="bilinear"):
140
152
  """Return the longitude and latitude from grid coordinates"""
141
153
  if method == "bilinear": # More accurate
142
154
  return self.xy2ll(X, Y)
143
- else: # containing grid cell, less accurate
144
- I = X.round().astype("int") - self.i0
145
- J = Y.round().astype("int") - self.j0
146
- return self.lon[J, I], self.lat[J, I]
147
-
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"""
148
164
  return (
149
- sample2D(self.lon, X - self.i0, Y - self.j0),
150
- sample2D(self.lat, X - self.i0, Y - self.j0),
165
+ (self.xmin + 0.5 < X)
166
+ & (X < self.xmax - 0.5)
167
+ & (self.ymin + 0.5 < Y)
168
+ & (Y < self.ymax - 0.5)
151
169
  )
152
170
 
153
- def ingrid(self, X, Y):
154
- """Returns True for points inside the subgrid"""
155
- return (self.xmin < X) & (X < self.xmax) & (self.ymin < Y) & (Y < self.ymax)
156
-
157
171
  def onland(self, X, Y):
158
172
  """Returns True for points on land"""
159
173
  I = X.round().astype(int) - self.i0
160
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
+
161
180
  return self.M[J, I] < 1
162
181
 
163
182
  # Error if point outside
@@ -165,6 +184,11 @@ class Grid:
165
184
  """Returns True for points at sea"""
166
185
  I = X.round().astype(int) - self.i0
167
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
+
168
192
  return self.M[J, I] > 0
169
193
 
170
194
  def xy2ll(self, X, Y):
@@ -194,7 +218,7 @@ class Forcing:
194
218
  logger.info("Initiating forcing")
195
219
 
196
220
  self._grid = grid # Get the grid object, make private?
197
-
221
+ # self.config = config["gridforce"]
198
222
  self.ibm_forcing = config["ibm_forcing"]
199
223
 
200
224
  # Test for glob, use MFDataset if needed
@@ -247,7 +271,7 @@ class Forcing:
247
271
  num_frames = [] # Available time frames in each file
248
272
  # change_times = [] # Times for change of file
249
273
  for fname in files:
250
- print(fname)
274
+ logging.info(f'Load {fname}')
251
275
  with Dataset(fname) as nc:
252
276
  # new_times = nc.variables['ocean_time'][:]
253
277
  new_times = nc.variables["time"][:]
@@ -313,6 +337,7 @@ class Forcing:
313
337
  # --------------
314
338
  # prestep = last forcing step < 0
315
339
  #
340
+
316
341
  V = [step for step in steps if step < 0]
317
342
  if V: # Forcing available before start time
318
343
  prestep = max(V)
@@ -334,11 +359,12 @@ class Forcing:
334
359
 
335
360
  elif steps[0] == 0:
336
361
  # Simulation start at first forcing time
362
+ # Runge-Kutta needs dU and dV in this case as well
337
363
  self.U, self.V = self._read_velocity(0)
338
364
  self.Unew, self.Vnew = self._read_velocity(steps[1])
339
365
  self.dU = (self.Unew - self.U) / steps[1]
340
366
  self.dV = (self.Vnew - self.V) / steps[1]
341
- # Syncronize
367
+ # Synchronize with start time
342
368
  self.Unew = self.U
343
369
  self.Vnew = self.V
344
370
  # Extrapolate to time step = -1
@@ -361,7 +387,7 @@ class Forcing:
361
387
  def update(self, t):
362
388
  """Update the fields to time step t"""
363
389
 
364
- # Read from config
390
+ # Read from config?
365
391
  interpolate_velocity_in_time = True
366
392
  interpolate_ibm_forcing_in_time = False
367
393
 
@@ -402,7 +428,7 @@ class Forcing:
402
428
 
403
429
  # Handle file opening/closing
404
430
  # Always read velocity before other fields
405
- logger.debug("Reading velocity for time step = {}".format(n))
431
+ logger.info("Reading velocity for time step = {}".format(n))
406
432
  first = True
407
433
  if first: # Open file initiallt
408
434
  self._nc = Dataset(self._files[self.file_idx[n]])
@@ -419,6 +445,7 @@ class Forcing:
419
445
  # Read the velocity
420
446
  U = self._nc.variables["u"][frame, :, self._grid.Ju, self._grid.Iu]
421
447
  V = self._nc.variables["v"][frame, :, self._grid.Jv, self._grid.Iv]
448
+
422
449
  # Scale if needed
423
450
  # Assume offset = 0 for velocity
424
451
  if self.scaled["U"]:
@@ -435,7 +462,6 @@ class Forcing:
435
462
 
436
463
  def _read_field(self, name, n):
437
464
  """Read a 3D field"""
438
- # print("IBM-forcing:", name)
439
465
  frame = self.frame_idx[n]
440
466
  F = self._nc.variables[name][frame, :, self._grid.J, self._grid.I]
441
467
  if self.scaled[name]:
@@ -568,7 +594,7 @@ def sdepth(H, Hc, C, stagger="rho", Vtransform=1):
568
594
  """
569
595
  H = np.asarray(H)
570
596
  Hshape = H.shape # Save the shape of H
571
- H = H.ravel() # and make H 1D for easy shape manipulation
597
+ H = H.ravel() # and make H 1D for easy shape maniplation
572
598
  C = np.asarray(C)
573
599
  N = len(C)
574
600
  outshape = (N,) + Hshape # Shape of output
@@ -584,13 +610,13 @@ def sdepth(H, Hc, C, stagger="rho", Vtransform=1):
584
610
  B = np.outer(C, H)
585
611
  return (A + B).reshape(outshape)
586
612
 
587
- elif Vtransform == 2: # New transform by Shchepetkin
613
+ if Vtransform == 2: # New transform by Shchepetkin
588
614
  N = Hc * S[:, None] + np.outer(C, H)
589
615
  D = 1.0 + Hc / H
590
616
  return (N / D).reshape(outshape)
591
617
 
592
- else:
593
- raise ValueError("Unknown Vtransform")
618
+ # else:
619
+ raise ValueError("Unknown Vtransform")
594
620
 
595
621
 
596
622
  # ------------------------
@@ -649,13 +675,15 @@ def sample3D(F, X, Y, K, A, method="bilinear"):
649
675
 
650
676
  """
651
677
 
652
- # print('sample3D: method =', method)
653
-
654
- # sjekk om 1-A eller A
655
678
  if method == "bilinear":
656
679
  # Find rho-point as lower left corner
657
680
  I = X.astype("int")
658
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
+
659
687
  P = X - I
660
688
  Q = Y - J
661
689
  W000 = (1 - P) * (1 - Q) * A
@@ -678,10 +706,15 @@ def sample3D(F, X, Y, K, A, method="bilinear"):
678
706
  + W111 * F[K - 1, J + 1, I + 1]
679
707
  )
680
708
 
681
- else: # method == 'nearest'
682
- I = X.round().astype("int")
683
- J = Y.round().astype("int")
684
- return F[K, J, I]
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]
685
718
 
686
719
 
687
720
  def sample3DUV(U, V, X, Y, K, A, method="bilinear"):
ladim/main.py CHANGED
@@ -47,7 +47,9 @@ def run():
47
47
 
48
48
  logging.basicConfig(
49
49
  level=logging.INFO,
50
- format='%(levelname)s:%(module)s - %(message)s')
50
+ format='%(asctime)s %(levelname)s:%(module)s - %(message)s',
51
+ datefmt='%Y-%m-%d %H:%M:%S',
52
+ )
51
53
 
52
54
  # ====================
53
55
  # Parse command line
ladim/model.py CHANGED
@@ -1,4 +1,7 @@
1
1
  import importlib
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
2
5
 
3
6
  from typing import TYPE_CHECKING
4
7
  if TYPE_CHECKING:
@@ -101,7 +104,26 @@ class Model:
101
104
 
102
105
  def load_class(name):
103
106
  pkg, cls = name.rsplit(sep='.', maxsplit=1)
104
- return getattr(importlib.import_module(pkg), cls)
107
+
108
+ # Check if "pkg" is an existing file
109
+ spec = None
110
+ module_name = None
111
+ file_name = pkg + '.py'
112
+ if Path(file_name).exists():
113
+ # This can return None if there were import errors
114
+ module_name = pkg
115
+ spec = importlib.util.spec_from_file_location(module_name, file_name)
116
+
117
+ # If pkg can not be interpreted as a file, use regular import
118
+ if spec is None:
119
+ return getattr(importlib.import_module(pkg), cls)
120
+
121
+ # File import
122
+ else:
123
+ module = importlib.util.module_from_spec(spec)
124
+ sys.modules[module_name] = module
125
+ spec.loader.exec_module(module)
126
+ return getattr(module, cls)
105
127
 
106
128
 
107
129
  class Module:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ladim
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: Lagrangian Advection and Diffusion Model
5
5
  Home-page: https://github.com/pnsaevik/ladim
6
6
  Author: Bjørn Ådlandsvik
@@ -1,10 +1,10 @@
1
- ladim/__init__.py,sha256=0i7KXjW3S0HuMuJqk69XKrsqQE9QUVTrXRDo6jNiFEM,51
1
+ ladim/__init__.py,sha256=1pOaNTt2-oJQ33nsnZyDsOulYErImxLErxjlfOb8Oyg,51
2
2
  ladim/__main__.py,sha256=8f07EMfxQllDZSgpak5ECyYHnfQFy8LaHl2xdC-aO9c,23
3
- ladim/config.py,sha256=6b0ikBrBnq_sSElgO2YcVtJHjQU0nyZsVyPV3q9fy5I,4233
3
+ ladim/config.py,sha256=tnTyKDeaDG1M9T5fiUGXG8Ezc_n7Xwm8l6--UpBJhaE,5449
4
4
  ladim/forcing.py,sha256=f4PpSwyilSScXeNyorTWLMgVTiat9htSLkCwAkRlJVM,3048
5
5
  ladim/grid.py,sha256=m6bQrGJ3cux7rqC8pbRXD86cOI-VQKF-XjP9m1jCIcY,2221
6
- ladim/main.py,sha256=EwBkmWwQG7I0_i-xjthSTeGsoALHH-MdQ1Vvf64lv_A,2838
7
- ladim/model.py,sha256=jpjq_ZSh7ULpwi3_RqDb-p5SG8WcdgCPaBkSpnNWblU,3137
6
+ ladim/main.py,sha256=6_blu3PYnDXaYdPxfZoukWsjN0o9vh7O8_-W2-aguAI,2894
7
+ ladim/model.py,sha256=iXClvieChhipCSZ-dDrmnjqwS4cuM53VpJv7oaJyQ88,3794
8
8
  ladim/output.py,sha256=Rz7iujvS7Z3LoABiJduQqyb3zPswNqhhFsywr3MLsBY,8373
9
9
  ladim/release.py,sha256=1j__9Gj0BD0CqVCM2KLZhio1Ia-hz1gbUIhTsa0J3Rg,8451
10
10
  ladim/sample.py,sha256=n8wRGd_VsW_qyQe1ZoTpmfZcdcwB929vsM8PoKG6JTs,8292
@@ -12,10 +12,10 @@ ladim/solver.py,sha256=sZvYgOxzJ-EItI-IB2y8_z8Tf-SJAQSrmydlhDRa7ZQ,755
12
12
  ladim/state.py,sha256=4XNIIx5sGjlqkZ6bg-dGbqzp8ujFNkHHFL2D9qCQA2w,4119
13
13
  ladim/tracker.py,sha256=VVX6T5CqiU6nGSCgLlSCC8w0UYhW273OGFE7ApPjdyI,5091
14
14
  ladim/utilities.py,sha256=r7-zShqJhh0cBctDUmtfw-GBOk1eTTYR4S72b0ouiSQ,994
15
- ladim/gridforce/ROMS.py,sha256=6y8ZIkAQgVPgK-zG53SKxFms4lJdkTCaP1doz7zuSyo,26965
15
+ ladim/gridforce/ROMS.py,sha256=DF5CSR2iJsPWAmTOrqYavARVtqokQbtAWLSCDWb_9f0,27525
16
16
  ladim/gridforce/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  ladim/gridforce/analytical.py,sha256=qI-1LJdjmnwwanzOdrsDZqwGgo73bT75CB7pMaxbHKM,1094
18
- ladim/gridforce/zROMS.py,sha256=t88mow91orUP-kMQiqmQ0_SnJKKG9xfycM5MMmdVljI,22853
18
+ ladim/gridforce/zROMS.py,sha256=4bnrmcXiWpCAUch9uqd_0XmyKRh-Ll6sFvIHiTbTOOg,23996
19
19
  ladim/ibms/__init__.py,sha256=GOG75jZDmNEiLr8brxrKqIlqVj-pNR7pnPP8FUKE6hU,565
20
20
  ladim/ibms/light.py,sha256=POltHmKkX8-q3t9wXyfcseCKEq9Bq-kX1WEJYsr1lNQ,2737
21
21
  ladim/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -24,9 +24,9 @@ postladim/cellcount.py,sha256=nCFu9iJmprubn4YmPB4W0VO02GfEb90Iif7D49w1Kss,2054
24
24
  postladim/kde_plot.py,sha256=GvMWzT6VxIeXKh1cnqaGzR-4jGG_WIHGMLPpRMXIpo4,1628
25
25
  postladim/particlefile.py,sha256=0aif9wYUJ-VrpQKeCef8wB5VCiBB-gWY6sxNCUYviTA,4889
26
26
  postladim/variable.py,sha256=-2aihoppYMMmpSpCqaF31XvpinTMaH3Y01-USDIkbBc,6587
27
- ladim-2.0.2.dist-info/LICENSE,sha256=BgtXyjNr6Ly9nQ7ZLXKpV3r5kWRLnh5MiN0dxp0Bvfc,1085
28
- ladim-2.0.2.dist-info/METADATA,sha256=sjw274vLpD7oaf1g96Ik4VEI0c2grbLVml7nVXTVfoU,1841
29
- ladim-2.0.2.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
30
- ladim-2.0.2.dist-info/entry_points.txt,sha256=JDlNJo87GJaOkH0-BpAzTPLCrZcuPSdSlHNQ4XmnoRg,41
31
- ladim-2.0.2.dist-info/top_level.txt,sha256=TK8Gl7d6MsrAQvqKG4b6YJCbB4UL46Se3SzsI-sJAuc,16
32
- ladim-2.0.2.dist-info/RECORD,,
27
+ ladim-2.0.4.dist-info/LICENSE,sha256=BgtXyjNr6Ly9nQ7ZLXKpV3r5kWRLnh5MiN0dxp0Bvfc,1085
28
+ ladim-2.0.4.dist-info/METADATA,sha256=9zVlHY1N0c5JKYGu9_ayPTmBVcPnzHaj9i8kJgLRgOU,1841
29
+ ladim-2.0.4.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
30
+ ladim-2.0.4.dist-info/entry_points.txt,sha256=JDlNJo87GJaOkH0-BpAzTPLCrZcuPSdSlHNQ4XmnoRg,41
31
+ ladim-2.0.4.dist-info/top_level.txt,sha256=TK8Gl7d6MsrAQvqKG4b6YJCbB4UL46Se3SzsI-sJAuc,16
32
+ ladim-2.0.4.dist-info/RECORD,,
File without changes
File without changes