-
Notifications
You must be signed in to change notification settings - Fork 47
Monte Carlo Method for Displacement Dynamic #1647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 13 commits
8f7f437
6cb90ef
ecc44b2
d9cb6f9
0490971
76c7d45
3582bd8
2e9bacf
91c2705
3b5cb31
6a1bd13
6106384
43d1078
4c78097
4f10f47
715f02d
34b666e
422d166
3d346a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -14,12 +14,15 @@ | |||||
@numba.njit(**{**conf.JIT_FLAGS, **{"parallel": False}}) | ||||||
# pylint: disable=too-many-arguments | ||||||
def calculate_displacement_body_common( | ||||||
dim, droplet, scheme, _l, _r, displacement, courant, position_in_cell, n_substeps | ||||||
dim, droplet, scheme, _l, _r, displacement, courant, position_in_cell, cell_id, n_substeps, enable_monte_carlo, rng | ||||||
): | ||||||
displacement[dim, droplet] = scheme( | ||||||
position_in_cell[dim, droplet], | ||||||
cell_id[droplet], | ||||||
courant[_l] / n_substeps, | ||||||
courant[_r] / n_substeps, | ||||||
enable_monte_carlo, | ||||||
rng.uniform(0.,1.) | ||||||
) | ||||||
|
||||||
|
||||||
|
@@ -28,7 +31,7 @@ class DisplacementMethods(BackendMethods): | |||||
@numba.njit(**{**conf.JIT_FLAGS, **{"parallel": False, "cache": False}}) | ||||||
# pylint: disable=too-many-arguments | ||||||
def calculate_displacement_body_1d( | ||||||
dim, scheme, displacement, courant, cell_origin, position_in_cell, n_substeps | ||||||
dim, scheme, displacement, courant, cell_origin, position_in_cell, cell_id, n_substeps, enable_monte_carlo, rng | ||||||
): | ||||||
length = displacement.shape[1] | ||||||
for droplet in numba.prange(length): # pylint: disable=not-an-iterable | ||||||
|
@@ -44,14 +47,17 @@ def calculate_displacement_body_1d( | |||||
displacement, | ||||||
courant, | ||||||
position_in_cell, | ||||||
cell_id, | ||||||
n_substeps, | ||||||
enable_monte_carlo, | ||||||
rng, | ||||||
|
rng, | |
rng[droplet], |
The same for 2d
and 3d
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to previous command it is how the code would look like here. It is the same for 2d
and 3d
rng, | |
rng.uniform(0., 1., displacement.data.shape[1]), |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,8 +24,11 @@ def __calculate_displacement_body(self): | |
"courant_shape_0", | ||
"courant_shape_1", | ||
"cell_origin", | ||
"cell_id", | ||
"position_in_cell", | ||
"n_substeps", | ||
"enable_monte_carlo", | ||
"u01", | ||
), | ||
name_iter="i", | ||
body=f""" | ||
|
@@ -52,8 +55,11 @@ def __calculate_displacement_body(self): | |
displacement[i + n_sd * dim] = { | ||
self.formulae.particle_advection.displacement.c_inline( | ||
position_in_cell="position_in_cell[i + n_sd * dim]", | ||
cell_id="cell_id[i + n_sd * dim]", | ||
c_l="courant[_l] / n_substeps", | ||
c_r="courant[_r] / n_substeps" | ||
c_r="courant[_r] / n_substeps", | ||
enable_monte_carlo="false", | ||
u01="u01" | ||
) | ||
}; | ||
""".replace( | ||
|
@@ -96,7 +102,7 @@ def __flag_precipitated_body(self): | |
|
||
@nice_thrust(**NICE_THRUST_FLAGS) | ||
def calculate_displacement( | ||
self, *, dim, displacement, courant, cell_origin, position_in_cell, n_substeps | ||
self, *, dim, displacement, courant, cell_origin, position_in_cell, cell_id, n_substeps, enable_monte_carlo, rng | ||
): | ||
n_dim = len(courant.shape) | ||
n_sd = position_in_cell.shape[1] | ||
|
@@ -110,8 +116,11 @@ def calculate_displacement( | |
trtc.DVInt64(courant.shape[0]), | ||
trtc.DVInt64(courant.shape[1] if n_dim > 2 else -1), | ||
cell_origin.data, | ||
cell_id.data, | ||
position_in_cell.data, | ||
trtc.DVInt64(n_substeps), | ||
trtc.DVBool(enable_monte_carlo), | ||
trtc.DVDouble(1.0) # TODO | ||
|
||
), | ||
) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,12 +21,14 @@ class Displacement: # pylint: disable=too-many-instance-attributes | |
def __init__( | ||
self, | ||
enable_sedimentation=False, | ||
enable_monte_carlo=False, | ||
precipitation_counting_level_index: int = 0, | ||
adaptive=DEFAULTS.adaptive, | ||
rtol=DEFAULTS.rtol, | ||
): # pylint: disable=too-many-arguments | ||
self.particulator = None | ||
self.enable_sedimentation = enable_sedimentation | ||
self.enable_monte_carlo = enable_monte_carlo | ||
self.dimension = None | ||
self.grid = None | ||
self.courant = None | ||
|
@@ -101,11 +103,12 @@ def __call__(self): | |
# TIP: not need all array only [idx[:sd_num]] | ||
cell_origin = self.particulator.attributes["cell origin"] | ||
position_in_cell = self.particulator.attributes["position in cell"] | ||
cell_id = self.particulator.attributes["cell id"] | ||
|
||
self.precipitation_mass_in_last_step = 0.0 | ||
for _ in range(self._n_substeps): | ||
self.calculate_displacement( | ||
self.displacement, self.courant, cell_origin, position_in_cell | ||
self.displacement, self.courant, cell_origin, position_in_cell, cell_id | ||
) | ||
self.update_position(position_in_cell, self.displacement) | ||
if self.enable_sedimentation: | ||
|
@@ -122,16 +125,23 @@ def __call__(self): | |
self.particulator.attributes.mark_updated(key) | ||
|
||
def calculate_displacement( | ||
self, displacement, courant, cell_origin, position_in_cell | ||
self, displacement, courant, cell_origin, position_in_cell, cell_id | ||
): | ||
if self.enable_sedimentation and self.enable_monte_carlo: | ||
dt = self.particulator.dt / self._n_substeps | ||
dt_over_dz = dt / self.particulator.mesh.dz | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From above to variables names it is not obvious what they represent. Maybe delta_time and delta_altitude (if z is altitude) would be better? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Variable namingThank you for the proposed update concerning the naming of the certain variables! I agree that clarity in variable naming is important, for both maintainability and readability of the code. In this case, however, the names With that being said, we can consider adding a short inline comment to clarify their meaning if that would help in understanding the functionality. |
||
courant += self.particulator.attributes["relative fall velocity"] / dt_over_dz | ||
|
||
self.particulator.calculate_displacement( | ||
displacement=displacement, | ||
courant=courant, | ||
cell_origin=cell_origin, | ||
cell_id=cell_id, | ||
position_in_cell=position_in_cell, | ||
n_substeps=self._n_substeps, | ||
enable_monte_carlo=self.enable_monte_carlo, | ||
) | ||
if self.enable_sedimentation: | ||
if self.enable_sedimentation and not self.enable_monte_carlo: | ||
displacement_z = displacement[self.dimension - 1, :] | ||
dt = self.particulator.dt / self._n_substeps | ||
dt_over_dz = dt / self.particulator.mesh.dz | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -426,16 +426,19 @@ def flag_out_of_column(self): | |
self.attributes.sanitize() | ||
|
||
def calculate_displacement( | ||
self, *, displacement, courant, cell_origin, position_in_cell, n_substeps | ||
self, *, displacement, courant, cell_origin, cell_id, position_in_cell, n_substeps, enable_monte_carlo | ||
): | ||
for dim in range(len(self.environment.mesh.grid)): | ||
self.backend.calculate_displacement( | ||
dim=dim, | ||
displacement=displacement, | ||
courant=courant[dim], | ||
cell_origin=cell_origin, | ||
cell_id=cell_id, | ||
position_in_cell=position_in_cell, | ||
n_substeps=n_substeps, | ||
enable_monte_carlo=enable_monte_carlo, | ||
rng=self.Random(1,1).generator, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function slowly starts being too complex. There are at least two flags used in a little bit confusing way (if flag1 and flag2 and after that if flag1 and not flag2). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the displacement calculation, the Random generator is instantiated with a hardcoded seed (seed=1) for each backend call |
||
) | ||
|
||
def isotopic_fractionation(self, heavy_isotopes: tuple): | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,12 +1,12 @@ | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
basic explicit-in-space Euler scheme | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
import numpy as np | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
class ExplicitInSpace: # pylint: disable=too-few-public-methods | ||||||||||||||||||||||||||||
def __init__(self, _): | ||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@staticmethod | ||||||||||||||||||||||||||||
def displacement(_, position_in_cell, c_l, c_r): | ||||||||||||||||||||||||||||
return c_l * (1 - position_in_cell) + c_r * position_in_cell | ||||||||||||||||||||||||||||
def displacement(_, position_in_cell, cell_id, c_l, c_r, enable_monte_carlo, u01): | ||||||||||||||||||||||||||||
return (position_in_cell + (np.floor(np.abs(max(c_l, c_r))) * np.sign(np.abs(max(c_l, c_r)) / max(c_l, c_r))) + (np.abs(max(c_l, c_r)) > u01) * np.sign(np.abs(max(c_l, c_r)) / max(c_l, c_r))) if enable_monte_carlo else (c_l * (1 - position_in_cell) + c_r * position_in_cell) | ||||||||||||||||||||||||||||
|
def displacement(_, position_in_cell, cell_id, c_l, c_r, enable_monte_carlo, u01): | |
return (position_in_cell + (np.floor(np.abs(max(c_l, c_r))) * np.sign(np.abs(max(c_l, c_r)) / max(c_l, c_r))) + (np.abs(max(c_l, c_r)) > u01) * np.sign(np.abs(max(c_l, c_r)) / max(c_l, c_r))) if enable_monte_carlo else (c_l * (1 - position_in_cell) + c_r * position_in_cell) | |
def displacement(_, position_in_cell, cell_id, c_l, c_r, enable_monte_carlo, u01): | |
if enable_monte_carlo: | |
max_c = max(c_l, c_r) | |
abs_max_c = np.abs(max_c) | |
sign_max_c = np.sign(abs_max_c / max_c) | |
floor_term = np.floor(abs_max_c) * sign_max_c | |
jump_term = (abs_max_c > u01) * sign_max_c | |
return position_in_cell + floor_term + jump_term | |
else: | |
numerator = c_l * (1 - position_in_cell) + c_r * position_in_cell | |
return numerator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed the file to make it more readable :)
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,12 +1,13 @@ | ||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||
eqs. 14-16 in [Arabas et al. 2015](https://doi.org/10.5194/gmd-8-1677-2015) | ||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
import numpy as np | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
class ImplicitInSpace: # pylint: disable=too-few-public-methods | ||||||||||||||||||||||||||||||||||
def __init__(self, _): | ||||||||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
@staticmethod | ||||||||||||||||||||||||||||||||||
def displacement(_, position_in_cell, c_l, c_r): | ||||||||||||||||||||||||||||||||||
return (c_l * (1 - position_in_cell) + c_r * position_in_cell) / (1 - c_r + c_l) | ||||||||||||||||||||||||||||||||||
def displacement(_, position_in_cell, cell_id, c_l, c_r, enable_monte_carlo, u01): | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return (position_in_cell + (np.floor(np.abs(max(c_l, c_r))) * np.sign(np.abs(max(c_l, c_r)) / max(c_l, c_r))) + (np.abs(max(c_l, c_r)) > u01) * np.sign(np.abs(max(c_l, c_r)) / max(c_l, c_r))) if enable_monte_carlo else ((c_l * (1 - position_in_cell) + c_r * position_in_cell) / (1 - c_r + c_l)) | ||||||||||||||||||||||||||||||||||
|
def displacement(_, position_in_cell, cell_id, c_l, c_r, enable_monte_carlo, u01): | |
return (position_in_cell + (np.floor(np.abs(max(c_l, c_r))) * np.sign(np.abs(max(c_l, c_r)) / max(c_l, c_r))) + (np.abs(max(c_l, c_r)) > u01) * np.sign(np.abs(max(c_l, c_r)) / max(c_l, c_r))) if enable_monte_carlo else ((c_l * (1 - position_in_cell) + c_r * position_in_cell) / (1 - c_r + c_l)) | |
def displacement(_, position_in_cell, cell_id, c_l, c_r, enable_monte_carlo, u01): | |
if enable_monte_carlo: | |
max_c = max(c_l, c_r) | |
abs_max_c = np.abs(max_c) | |
sign_max_c = np.sign(abs_max_c / max_c) | |
floor_term = np.floor(abs_max_c) * sign_max_c | |
jump_term = (abs_max_c > u01) * sign_max_c | |
return position_in_cell + floor_term + jump_term | |
else: | |
numerator = c_l * (1 - position_in_cell) + c_r * position_in_cell | |
denominator = 1 - c_r + c_l | |
normalized_position = numerator / denominator | |
return normalized_position |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed the file to make it more readable :)
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -97,15 +97,16 @@ def test_calculate_displacement(backend_class): | |
) | ||
settings.positions = [[weight], [0]] | ||
sut, particulator = settings.get_displacement( | ||
backend_class, scheme="ExplicitInSpace", adaptive=False | ||
backend_class, scheme="ExplicitInSpace", adaptive=False, enable_monte_carlo=False | ||
) | ||
|
||
# Act | ||
sut.calculate_displacement( | ||
sut.displacement, | ||
sut.courant, | ||
particulator.attributes["cell origin"], | ||
particulator.attributes["position in cell"], | ||
displacement=sut.displacement, | ||
courant=sut.courant, | ||
cell_origin=particulator.attributes["cell origin"], | ||
position_in_cell=particulator.attributes["position in cell"], | ||
cell_id=particulator.attributes["cell id"], | ||
) | ||
|
||
# Assert | ||
|
@@ -127,15 +128,16 @@ def test_calculate_displacement_dim1(backend_class): | |
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tests test_calculate_displacement and test_calculate_displacement_dim1 are very similar. They both test the calculate_displacement method, but with different input data to check behavior along different dimensions. This structural duplication can be reduced.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are absolutely right! Those two test methods are nearly identical and can definitely be refactored into a single parametrized test. Here is how I would implement your suggestion: @staticmethod
@pytest.mark.parametrize(
"courant_field_data, positions, asserted_dimension, asserted_slice",
[
(
(np.array([[0.1, 0.2]]).T, np.array([[0, 0]])),
[[0.25], [0]],
0,
slice(0, 1)
),
(
(np.array([[0, 0]]).T, np.array([[0.1, 0.2]])),
[[0], [0.25]],
1,
slice(0, 1)
),
],
)
def test_calculate_displacement_by_dimension(
backend_class, courant_field_data, positions, asserted_dimension, asserted_slice
):
# Arrange
settings = DisplacementSettings()
value_a = 0.1
value_b = 0.2
weight = 0.25
settings.courant_field_data = courant_field_data
settings.positions = positions
sut, particulator = settings.get_displacement(
backend_class, scheme="ExplicitInSpace", adaptive=False, enable_monte_carlo=False
)
# Act
sut.calculate_displacement(
displacement=sut.displacement,
courant=sut.courant,
cell_origin=particulator.attributes["cell origin"],
position_in_cell=particulator.attributes["position in cell"],
cell_id=particulator.attributes["cell id"],
)
# Assert
np.testing.assert_equal(
sut.displacement[asserted_dimension, asserted_slice].to_ndarray(),
(1 - weight) * value_a + weight * value_b,
) |
||
settings.positions = [[0], [weight]] | ||
sut, particulator = settings.get_displacement( | ||
backend_class, scheme="ExplicitInSpace", adaptive=False | ||
backend_class, scheme="ExplicitInSpace", adaptive=False, enable_monte_carlo=False | ||
) | ||
|
||
# Act | ||
sut.calculate_displacement( | ||
sut.displacement, | ||
sut.courant, | ||
particulator.attributes["cell origin"], | ||
particulator.attributes["position in cell"], | ||
displacement=sut.displacement, | ||
courant=sut.courant, | ||
cell_origin=particulator.attributes["cell origin"], | ||
position_in_cell=particulator.attributes["position in cell"], | ||
cell_id=particulator.attributes["cell id"], | ||
) | ||
|
||
# Assert | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you pass
rng
object which is numpy Random Generator for this jit function. I think it would be better to pass already created random uniform distribution as array (see supported types for numba)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added your changes and the tests that I run locally run correctly :)