ladim 1.3.4__py3-none-any.whl → 2.0.1__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 +1 -1
- ladim/__main__.py +2 -0
- ladim/config.py +125 -0
- ladim/forcing.py +95 -0
- ladim/grid.py +79 -0
- ladim/gridforce/ROMS.py +32 -5
- ladim/gridforce/__init__.py +0 -1
- ladim/ibms/__init__.py +19 -4
- ladim/main.py +1 -1
- ladim/model.py +64 -29
- ladim/output.py +246 -0
- ladim/plugins/__init__.py +0 -0
- ladim/release.py +241 -0
- ladim/sample.py +3 -0
- ladim/{timestepper.py → solver.py} +5 -5
- ladim/state.py +142 -0
- ladim/tracker.py +165 -0
- ladim/utilities.py +5 -0
- {ladim-1.3.4.dist-info → ladim-2.0.1.dist-info}/METADATA +1 -1
- ladim-2.0.1.dist-info/RECORD +32 -0
- {ladim-1.3.4.dist-info → ladim-2.0.1.dist-info}/WHEEL +1 -1
- ladim/configuration/__init__.py +0 -1
- ladim/configuration/legacy.py +0 -425
- ladim/configuration/modularized.py +0 -22
- ladim/gridforce/legacy.py +0 -103
- ladim/ibms/legacy.py +0 -34
- ladim/output/__init__.py +0 -1
- ladim/output/legacy.py +0 -247
- ladim/release/__init__.py +0 -1
- ladim/release/legacy.py +0 -316
- ladim/state/__init__.py +0 -1
- ladim/state/legacy.py +0 -126
- ladim/tracker/__init__.py +0 -1
- ladim/tracker/legacy.py +0 -225
- ladim-1.3.4.dist-info/RECORD +0 -36
- {ladim-1.3.4.dist-info → ladim-2.0.1.dist-info}/LICENSE +0 -0
- {ladim-1.3.4.dist-info → ladim-2.0.1.dist-info}/entry_points.txt +0 -0
- {ladim-1.3.4.dist-info → ladim-2.0.1.dist-info}/top_level.txt +0 -0
ladim/__init__.py
CHANGED
ladim/__main__.py
ADDED
ladim/config.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions for parsing configuration parameters.
|
|
3
|
+
|
|
4
|
+
The module contains functions for parsing input configuration
|
|
5
|
+
parameters, appending default values and converting between
|
|
6
|
+
different versions of config file formats.
|
|
7
|
+
"""
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def configure(module_conf):
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
# Handle variations of input config type
|
|
15
|
+
if isinstance(module_conf, dict):
|
|
16
|
+
config_dict = module_conf
|
|
17
|
+
else:
|
|
18
|
+
config_dict = yaml.safe_load(module_conf)
|
|
19
|
+
|
|
20
|
+
if 'version' not in config_dict:
|
|
21
|
+
if 'particle_release' in config_dict:
|
|
22
|
+
config_dict['version'] = 1
|
|
23
|
+
else:
|
|
24
|
+
config_dict['version'] = 2
|
|
25
|
+
|
|
26
|
+
return _versioned_configure(config_dict)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _versioned_configure(config_dict):
|
|
30
|
+
if config_dict['version'] == 1:
|
|
31
|
+
config_dict = _convert_1_to_2(config_dict)
|
|
32
|
+
|
|
33
|
+
return config_dict
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _convert_1_to_2(c):
|
|
37
|
+
# Read timedelta
|
|
38
|
+
dt_value, dt_unit = c['numerics']['dt']
|
|
39
|
+
dt_sec = np.timedelta64(dt_value, dt_unit).astype('timedelta64[s]').astype('int64')
|
|
40
|
+
|
|
41
|
+
# Read output variables
|
|
42
|
+
outvars = dict()
|
|
43
|
+
outvar_names = c['output_variables'].get('particle', []) + c['output_variables'].get('instance', [])
|
|
44
|
+
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'
|
|
50
|
+
|
|
51
|
+
# 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'] = {
|
|
67
|
+
k: np.float64(0)
|
|
68
|
+
for k in ibmvars
|
|
69
|
+
if k not in relconf['colnames']
|
|
70
|
+
}
|
|
71
|
+
|
|
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'] = {
|
|
86
|
+
k: v
|
|
87
|
+
for k, v in ibmconf_legacy.items()
|
|
88
|
+
if k != 'ibm_module'
|
|
89
|
+
}
|
|
90
|
+
|
|
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
|
ladim/forcing.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from .model import Model, Module
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Forcing(Module):
|
|
5
|
+
def __init__(self, model: Model):
|
|
6
|
+
super().__init__(model)
|
|
7
|
+
|
|
8
|
+
def velocity(self, X, Y, Z, tstep=0.0):
|
|
9
|
+
raise NotImplementedError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RomsForcing(Forcing):
|
|
13
|
+
def __init__(self, model: Model, file, variables=None, **conf):
|
|
14
|
+
"""
|
|
15
|
+
Forcing module which uses output data from the ROMS ocean model
|
|
16
|
+
|
|
17
|
+
:param model: Parent model
|
|
18
|
+
:param file: Glob pattern for the input files
|
|
19
|
+
:param variables: A mapping of variable names to interpolation
|
|
20
|
+
specifications. Each interpolaction specification consists of 0-4
|
|
21
|
+
of the letters "xyzt". Coordinates that are listed in the string are
|
|
22
|
+
interpolated linearly, while the remaining ones use nearest-neighbor
|
|
23
|
+
interpolation. Some default configurations are defined:
|
|
24
|
+
|
|
25
|
+
.. code-block:: json
|
|
26
|
+
{
|
|
27
|
+
"temp": "xyzt",
|
|
28
|
+
"salt": "xyzt",
|
|
29
|
+
"u": "xt",
|
|
30
|
+
"v": "yt",
|
|
31
|
+
"w": "zt",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
:param conf: Legacy config dict
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(model)
|
|
38
|
+
|
|
39
|
+
# Apply default interpolation configs
|
|
40
|
+
variables = variables or dict()
|
|
41
|
+
default_vars = dict(u="xt", v="yt", w="zt", temp="xyzt", salt="xyzt")
|
|
42
|
+
self.variables = {**default_vars, **variables}
|
|
43
|
+
|
|
44
|
+
grid_ref = GridReference(model)
|
|
45
|
+
legacy_conf = dict(
|
|
46
|
+
gridforce=dict(
|
|
47
|
+
input_file=file,
|
|
48
|
+
),
|
|
49
|
+
ibm_forcing=conf.get('ibm_forcing', []),
|
|
50
|
+
start_time=conf.get('start_time', None),
|
|
51
|
+
stop_time=conf.get('stop_time', None),
|
|
52
|
+
dt=conf.get('dt', None),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
from .model import load_class
|
|
56
|
+
LegacyForcing = load_class(conf.get('legacy_module', 'ladim.gridforce.ROMS.Forcing'))
|
|
57
|
+
|
|
58
|
+
# Allow gridforce module in current directory
|
|
59
|
+
import sys
|
|
60
|
+
import os
|
|
61
|
+
sys.path.insert(0, os.getcwd())
|
|
62
|
+
# Import correct gridforce_module
|
|
63
|
+
self.forcing = LegacyForcing(legacy_conf, grid_ref)
|
|
64
|
+
# self.steps = self.forcing.steps
|
|
65
|
+
# self.U = self.forcing.U
|
|
66
|
+
# self.V = self.forcing.V
|
|
67
|
+
|
|
68
|
+
def update(self):
|
|
69
|
+
elapsed = self.model.solver.time - self.model.solver.start
|
|
70
|
+
t = elapsed // self.model.solver.step
|
|
71
|
+
|
|
72
|
+
self.forcing.update(t)
|
|
73
|
+
|
|
74
|
+
# Update state variables by sampling the field
|
|
75
|
+
x, y, z = self.model.state['X'], self.model.state['Y'], self.model.state['Z']
|
|
76
|
+
for v in self.variables:
|
|
77
|
+
if v in self.model.state:
|
|
78
|
+
self.model.state[v] = self.field(x, y, z, v)
|
|
79
|
+
|
|
80
|
+
def velocity(self, X, Y, Z, tstep=0.0):
|
|
81
|
+
return self.forcing.velocity(X, Y, Z, tstep=tstep)
|
|
82
|
+
|
|
83
|
+
def field(self, X, Y, Z, name):
|
|
84
|
+
return self.forcing.field(X, Y, Z, name)
|
|
85
|
+
|
|
86
|
+
def close(self):
|
|
87
|
+
return self.forcing.close()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class GridReference:
|
|
91
|
+
def __init__(self, modules: Model):
|
|
92
|
+
self.modules = modules
|
|
93
|
+
|
|
94
|
+
def __getattr__(self, item):
|
|
95
|
+
return getattr(self.modules.grid.grid, item)
|
ladim/grid.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from .model import Model, Module
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Grid(Module):
|
|
5
|
+
def __init__(self, model: Model):
|
|
6
|
+
super().__init__(model)
|
|
7
|
+
|
|
8
|
+
def ingrid(self, X, Y):
|
|
9
|
+
raise NotImplementedError
|
|
10
|
+
|
|
11
|
+
def sample_metric(self, X, Y):
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
|
|
14
|
+
def atsea(self, X, Y):
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
|
|
17
|
+
def ll2xy(self, lon, lat):
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
def xy2ll(self, x, y):
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RomsGrid(Grid):
|
|
25
|
+
def __init__(self, model: Model, **conf):
|
|
26
|
+
super().__init__(model)
|
|
27
|
+
|
|
28
|
+
legacy_conf = dict(
|
|
29
|
+
gridforce=dict(
|
|
30
|
+
input_file=conf['file'],
|
|
31
|
+
),
|
|
32
|
+
start_time=conf.get('start_time', None),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from .model import load_class
|
|
36
|
+
LegacyGrid = load_class(conf.get('legacy_module', 'ladim.gridforce.ROMS.Grid'))
|
|
37
|
+
|
|
38
|
+
# Allow gridforce module in current directory
|
|
39
|
+
import sys
|
|
40
|
+
import os
|
|
41
|
+
sys.path.insert(0, os.getcwd())
|
|
42
|
+
# Import correct gridforce_module
|
|
43
|
+
# gridforce_module = importlib.import_module(config["gridforce_module"])
|
|
44
|
+
self.grid = LegacyGrid(legacy_conf)
|
|
45
|
+
self.xmin = self.grid.xmin
|
|
46
|
+
self.xmax = self.grid.xmax
|
|
47
|
+
self.ymin = self.grid.ymin
|
|
48
|
+
self.ymax = self.grid.ymax
|
|
49
|
+
|
|
50
|
+
def sample_metric(self, X, Y):
|
|
51
|
+
"""Sample the metric coefficients"""
|
|
52
|
+
return self.grid.sample_metric(X, Y)
|
|
53
|
+
|
|
54
|
+
def sample_depth(self, X, Y):
|
|
55
|
+
"""Return the depth of grid cells"""
|
|
56
|
+
return self.grid.sample_depth(X, Y)
|
|
57
|
+
|
|
58
|
+
def lonlat(self, X, Y, method=None):
|
|
59
|
+
"""Return the longitude and latitude from grid coordinates"""
|
|
60
|
+
return self.grid.lonlat(X, Y, method=method)
|
|
61
|
+
|
|
62
|
+
def ingrid(self, X, Y):
|
|
63
|
+
"""Returns True for points inside the subgrid"""
|
|
64
|
+
return self.grid.ingrid(X, Y)
|
|
65
|
+
|
|
66
|
+
def onland(self, X, Y):
|
|
67
|
+
"""Returns True for points on land"""
|
|
68
|
+
return self.grid.onland(X, Y)
|
|
69
|
+
|
|
70
|
+
# Error if point outside
|
|
71
|
+
def atsea(self, X, Y):
|
|
72
|
+
"""Returns True for points at sea"""
|
|
73
|
+
return self.grid.atsea(X, Y)
|
|
74
|
+
|
|
75
|
+
def ll2xy(self, lon, lat):
|
|
76
|
+
return self.grid.ll2xy(lon, lat)
|
|
77
|
+
|
|
78
|
+
def xy2ll(self, X, Y):
|
|
79
|
+
return self.grid.xy2ll(X, Y)
|
ladim/gridforce/ROMS.py
CHANGED
|
@@ -168,6 +168,10 @@ class Grid:
|
|
|
168
168
|
I = X.round().astype(int) - self.i0
|
|
169
169
|
J = Y.round().astype(int) - self.j0
|
|
170
170
|
|
|
171
|
+
# Constrain to valid indices
|
|
172
|
+
I = np.minimum(np.maximum(I, 0), self.dx.shape[-1] - 2)
|
|
173
|
+
J = np.minimum(np.maximum(J, 0), self.dx.shape[-2] - 2)
|
|
174
|
+
|
|
171
175
|
# Metric is conform for PolarStereographic
|
|
172
176
|
A = self.dx[J, I]
|
|
173
177
|
return A, A
|
|
@@ -207,6 +211,11 @@ class Grid:
|
|
|
207
211
|
"""Returns True for points at sea"""
|
|
208
212
|
I = X.round().astype(int) - self.i0
|
|
209
213
|
J = Y.round().astype(int) - self.j0
|
|
214
|
+
|
|
215
|
+
# Constrain to valid indices
|
|
216
|
+
I = np.minimum(np.maximum(I, 0), self.M.shape[-1] - 1)
|
|
217
|
+
J = np.minimum(np.maximum(J, 0), self.M.shape[-2] - 1)
|
|
218
|
+
|
|
210
219
|
return self.M[J, I] > 0
|
|
211
220
|
|
|
212
221
|
def xy2ll(self, X, Y):
|
|
@@ -239,6 +248,10 @@ class Forcing:
|
|
|
239
248
|
# self.config = config["gridforce"]
|
|
240
249
|
self.ibm_forcing = config["ibm_forcing"]
|
|
241
250
|
|
|
251
|
+
config = config.copy()
|
|
252
|
+
config["start_time"] = np.datetime64(config["start_time"])
|
|
253
|
+
config["stop_time"] = np.datetime64(config["stop_time"])
|
|
254
|
+
|
|
242
255
|
files = self.find_files(config["gridforce"])
|
|
243
256
|
numfiles = len(files)
|
|
244
257
|
if numfiles == 0:
|
|
@@ -319,12 +332,17 @@ class Forcing:
|
|
|
319
332
|
@staticmethod
|
|
320
333
|
def find_files(force_config):
|
|
321
334
|
"""Find (and sort) the forcing file(s)"""
|
|
322
|
-
|
|
335
|
+
|
|
336
|
+
# Use unix-style filenames to provide consistency between windows and linux
|
|
337
|
+
first_file = force_config.get("first_file", "").replace("\\", "/")
|
|
338
|
+
last_file = force_config.get("last_file", "").replace("\\", "/")
|
|
339
|
+
files = [f.replace("\\", "/") for f in glob.glob(force_config["input_file"])]
|
|
340
|
+
|
|
323
341
|
files.sort()
|
|
324
|
-
if
|
|
325
|
-
files = [f for f in files if f >=
|
|
326
|
-
if
|
|
327
|
-
files = [f for f in files if f <=
|
|
342
|
+
if first_file:
|
|
343
|
+
files = [f for f in files if f >= first_file]
|
|
344
|
+
if last_file:
|
|
345
|
+
files = [f for f in files if f <= last_file]
|
|
328
346
|
return files
|
|
329
347
|
|
|
330
348
|
@staticmethod
|
|
@@ -713,6 +731,10 @@ def z2s(z_rho, X, Y, Z):
|
|
|
713
731
|
I = np.around(X).astype("int")
|
|
714
732
|
J = np.around(Y).astype("int")
|
|
715
733
|
|
|
734
|
+
# Constrain to valid indices
|
|
735
|
+
I = np.minimum(np.maximum(I, 0), z_rho.shape[-1] - 1)
|
|
736
|
+
J = np.minimum(np.maximum(J, 0), z_rho.shape[-2] - 1)
|
|
737
|
+
|
|
716
738
|
# Vectorized searchsorted
|
|
717
739
|
K = np.sum(z_rho[:, J, I] < -Z, axis=0)
|
|
718
740
|
K = K.clip(1, kmax - 1)
|
|
@@ -747,6 +769,11 @@ def sample3D(F, X, Y, K, A, method="bilinear"):
|
|
|
747
769
|
# Find rho-point as lower left corner
|
|
748
770
|
I = X.astype("int")
|
|
749
771
|
J = Y.astype("int")
|
|
772
|
+
|
|
773
|
+
# Constrain to valid indices
|
|
774
|
+
I = np.minimum(np.maximum(I, 0), F.shape[-1] - 2)
|
|
775
|
+
J = np.minimum(np.maximum(J, 0), F.shape[-2] - 2)
|
|
776
|
+
|
|
750
777
|
P = X - I
|
|
751
778
|
Q = Y - J
|
|
752
779
|
W000 = (1 - P) * (1 - Q) * (1 - A)
|
ladim/gridforce/__init__.py
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .legacy import Grid, Forcing
|
ladim/ibms/__init__.py
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
from ..model import Model, Module
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IBM(Module):
|
|
5
|
+
def __init__(self, model: Model):
|
|
6
|
+
super().__init__(model)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LegacyIBM(IBM):
|
|
10
|
+
def __init__(self, model: Model, legacy_module, conf):
|
|
11
|
+
super().__init__(model)
|
|
12
|
+
|
|
13
|
+
from ..model import load_class
|
|
14
|
+
LegacyIbmClass = load_class(legacy_module + '.IBM')
|
|
15
|
+
self._ibm = LegacyIbmClass(conf)
|
|
4
16
|
|
|
5
17
|
def update(self):
|
|
6
|
-
|
|
18
|
+
grid = self.model['grid']
|
|
19
|
+
state = self.model['state']
|
|
20
|
+
forcing = self.model['forcing']
|
|
21
|
+
self._ibm.update_ibm(grid, state, forcing)
|
ladim/main.py
CHANGED
ladim/model.py
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from ladim.grid import Grid
|
|
6
|
+
from ladim.forcing import Forcing
|
|
7
|
+
from ladim.ibms import IBM
|
|
8
|
+
from ladim.output import Output
|
|
9
|
+
from ladim.release import Releaser
|
|
10
|
+
from ladim.state import State
|
|
11
|
+
from ladim.tracker import Tracker
|
|
12
|
+
from ladim.solver import Solver
|
|
10
13
|
|
|
11
14
|
DEFAULT_MODULES = dict(
|
|
12
|
-
grid='ladim.
|
|
13
|
-
forcing='ladim.
|
|
14
|
-
release='ladim.release.
|
|
15
|
-
state='ladim.state.
|
|
16
|
-
output='ladim.output.
|
|
15
|
+
grid='ladim.grid.RomsGrid',
|
|
16
|
+
forcing='ladim.forcing.RomsForcing',
|
|
17
|
+
release='ladim.release.TextFileReleaser',
|
|
18
|
+
state='ladim.state.DynamicState',
|
|
19
|
+
output='ladim.output.RaggedOutput',
|
|
17
20
|
ibm='ladim.ibms.IBM',
|
|
18
|
-
tracker='ladim.tracker.
|
|
19
|
-
|
|
21
|
+
tracker='ladim.tracker.HorizontalTracker',
|
|
22
|
+
solver='ladim.solver.Solver',
|
|
20
23
|
)
|
|
21
24
|
|
|
22
25
|
|
|
@@ -24,49 +27,62 @@ class Model:
|
|
|
24
27
|
def __init__(self, config):
|
|
25
28
|
module_names = (
|
|
26
29
|
'grid', 'forcing', 'release', 'state', 'output', 'ibm', 'tracker',
|
|
27
|
-
'
|
|
30
|
+
'solver',
|
|
28
31
|
)
|
|
29
32
|
|
|
30
33
|
self.modules = dict()
|
|
31
34
|
for name in module_names:
|
|
32
|
-
self.add_module(name, config
|
|
35
|
+
self.add_module(name, config.get(name, dict()))
|
|
33
36
|
|
|
34
37
|
def add_module(self, name, conf):
|
|
35
38
|
module_name = conf.get('module', DEFAULT_MODULES[name])
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
conf_without_module = {
|
|
40
|
+
k: v for k, v in conf.items()
|
|
41
|
+
if k != 'module'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
cls = load_class(module_name)
|
|
45
|
+
self.modules[name] = cls(self, **conf_without_module)
|
|
38
46
|
|
|
39
47
|
@property
|
|
40
|
-
def grid(self) ->
|
|
48
|
+
def grid(self) -> "Grid":
|
|
49
|
+
# noinspection PyTypeChecker
|
|
41
50
|
return self.modules.get('grid', None)
|
|
42
51
|
|
|
43
52
|
@property
|
|
44
|
-
def forcing(self) ->
|
|
53
|
+
def forcing(self) -> "Forcing":
|
|
54
|
+
# noinspection PyTypeChecker
|
|
45
55
|
return self.modules.get('forcing', None)
|
|
46
56
|
|
|
47
57
|
@property
|
|
48
|
-
def release(self) ->
|
|
58
|
+
def release(self) -> "Releaser":
|
|
59
|
+
# noinspection PyTypeChecker
|
|
49
60
|
return self.modules.get('release', None)
|
|
50
61
|
|
|
51
62
|
@property
|
|
52
|
-
def state(self) ->
|
|
63
|
+
def state(self) -> "State":
|
|
64
|
+
# noinspection PyTypeChecker
|
|
53
65
|
return self.modules.get('state', None)
|
|
54
66
|
|
|
55
67
|
@property
|
|
56
|
-
def output(self) ->
|
|
68
|
+
def output(self) -> "Output":
|
|
69
|
+
# noinspection PyTypeChecker
|
|
57
70
|
return self.modules.get('output', None)
|
|
58
71
|
|
|
59
72
|
@property
|
|
60
|
-
def ibm(self) ->
|
|
73
|
+
def ibm(self) -> "IBM":
|
|
74
|
+
# noinspection PyTypeChecker
|
|
61
75
|
return self.modules.get('ibm', None)
|
|
62
76
|
|
|
63
77
|
@property
|
|
64
|
-
def tracker(self) ->
|
|
78
|
+
def tracker(self) -> "Tracker":
|
|
79
|
+
# noinspection PyTypeChecker
|
|
65
80
|
return self.modules.get('tracker', None)
|
|
66
81
|
|
|
67
82
|
@property
|
|
68
|
-
def
|
|
69
|
-
|
|
83
|
+
def solver(self) -> "Solver":
|
|
84
|
+
# noinspection PyTypeChecker
|
|
85
|
+
return self.modules.get('solver', None)
|
|
70
86
|
|
|
71
87
|
def __getitem__(self, item):
|
|
72
88
|
return self.modules[item]
|
|
@@ -75,7 +91,7 @@ class Model:
|
|
|
75
91
|
return item in self.modules
|
|
76
92
|
|
|
77
93
|
def run(self):
|
|
78
|
-
self.
|
|
94
|
+
self.solver.run()
|
|
79
95
|
|
|
80
96
|
def close(self):
|
|
81
97
|
for m in self.modules.values():
|
|
@@ -83,6 +99,25 @@ class Model:
|
|
|
83
99
|
m.close()
|
|
84
100
|
|
|
85
101
|
|
|
86
|
-
def
|
|
102
|
+
def load_class(name):
|
|
87
103
|
pkg, cls = name.rsplit(sep='.', maxsplit=1)
|
|
88
104
|
return getattr(importlib.import_module(pkg), cls)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Module:
|
|
108
|
+
def __init__(self, model: Model):
|
|
109
|
+
self._model = model
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def model(self) -> Model:
|
|
113
|
+
return self._model
|
|
114
|
+
|
|
115
|
+
@model.setter
|
|
116
|
+
def model(self, value: Model):
|
|
117
|
+
self._model = value
|
|
118
|
+
|
|
119
|
+
def update(self):
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
def close(self):
|
|
123
|
+
pass
|