@@ -262,6 +262,8 @@ def __init__(self,
262
262
self .steps_calculation = 0 # Increase for each simulation step
263
263
self .elements_deactivated = self .ElementType () # Empty array
264
264
self .elements = self .ElementType () # Empty array
265
+ self ._elements_previous = None
266
+ self ._environment_previous = None
265
267
266
268
# Set up logging
267
269
logformat = '%(asctime)s %(levelname)-7s %(name)s:%(lineno)d: %(message)s'
@@ -570,55 +572,24 @@ def prepare_run(self):
570
572
def post_run (self ):
571
573
pass
572
574
573
- def store_present_positions (self , IDs = None , lons = None , lats = None ):
574
- """Store present element positions, in case they shall be moved back"""
575
- if self .get_config ('general:coastline_action' ) in ['previous' , 'stranding' ] or (
576
- 'general:seafloor_action' in self ._config
577
- and self .get_config ('general:seafloor_action' ) == 'previous' ):
578
- if not hasattr (self , 'previous_lon' ):
579
- self .previous_lon = np .ma .masked_all (self .num_elements_total ())
580
- self .previous_lat = np .ma .masked_all (self .num_elements_total ())
581
- if IDs is None :
582
- IDs = self .elements .ID
583
- lons = self .elements .lon
584
- lats = self .elements .lat
585
- self .newly_seeded_IDs = None
586
- else :
587
- # to check if seeded on land
588
- if len (IDs ) > 0 :
589
- self .newly_seeded_IDs = np .copy (IDs )
590
- else :
591
- self .newly_seeded_IDs = None
592
- self .previous_lon [IDs ] = np .copy (lons )
593
- self .previous_lat [IDs ] = np .copy (lats )
594
-
595
- def store_previous_variables (self ):
596
- """Store some environment variables, for access at next time step"""
597
-
598
- if not hasattr (self , 'store_previous' ):
599
- return
600
- if not hasattr (self , 'variables_previous' ):
601
- # Create ndarray to store previous variables
602
- dtype = [(var , np .float32 ) for var in self .store_previous ]
603
- self .variables_previous = np .array (np .full (
604
- self .num_elements_total (), np .nan ),
605
- dtype = dtype )
606
-
607
- # Copying variables_previous to environment_previous
608
- self .environment_previous = self .variables_previous [self .elements .ID ]
609
-
610
- # Use new values for new elements which have no previous value
611
- for var in self .store_previous :
612
- undefined = np .isnan (self .environment_previous [var ])
613
- self .environment_previous [var ][undefined ] = getattr (
614
- self .environment , var )[undefined ]
615
-
616
- self .environment_previous = self .environment_previous .view (np .recarray )
617
-
618
- for var in self .store_previous :
619
- self .variables_previous [var ][self .elements .ID ] = getattr (
620
- self .environment , var )
621
-
575
+ def update_previous_state (self ):
576
+ """Store some properties and variables, for access at next time step"""
577
+
578
+ # Retrieve previous from storage
579
+ if self ._environment_previous is not None :
580
+ # Update previous environment
581
+ self .environment_previous = self ._environment_previous .isel (trajectory = self .elements .ID )
582
+ # Store present environment variables
583
+ for var in self ._environment_previous :
584
+ self ._environment_previous [var ][self .elements .ID ] = self .environment [var ]
585
+
586
+ if self ._elements_previous is not None :
587
+ # Update previous elements
588
+ self .elements_previous = self ._elements_previous .isel (trajectory = self .elements .ID )
589
+ # Store present element properties
590
+ for var in self ._elements_previous :
591
+ self ._elements_previous [var ][self .elements .ID ] = getattr (self .elements , var )
592
+
622
593
def interact_with_coastline (self , final = False ):
623
594
"""Coastline interaction according to configuration setting"""
624
595
@@ -661,23 +632,24 @@ def interact_with_coastline(self, final=False):
661
632
for on_land_id , on_land_prev_id in zip (on_land , self .elements .ID [on_land ]):
662
633
lon = self .elements .lon [on_land_id ]
663
634
lat = self .elements .lat [on_land_id ]
664
- prev_lon = self .previous_lon [on_land_prev_id ]
665
- prev_lat = self .previous_lat [on_land_prev_id ]
666
-
635
+ prev_lon = self ._elements_previous .lon [on_land_prev_id ].data
636
+ prev_lat = self ._elements_previous .lat [on_land_prev_id ].data
667
637
step_degrees = float (coastline_approximation_precision )
668
638
669
639
x_degree_diff = np .abs (prev_lon - lon )
640
+ y_degree_diff = np .abs (prev_lat - lat )
641
+ if x_degree_diff == 0 and y_degree_diff == 0 :
642
+ continue
670
643
x_samples = np .floor (x_degree_diff / step_degrees ).astype (np .int64 ) if x_degree_diff > step_degrees else 1
671
644
x = np .linspace (prev_lon , lon , x_samples )
672
645
673
- y_degree_diff = np .abs (prev_lat - lat )
674
646
y_samples = np .floor (y_degree_diff / step_degrees ).astype (np .int64 ) if y_degree_diff > step_degrees else 1
675
647
y = np .linspace (prev_lat , lat , y_samples )
676
648
677
649
xx , yy = np .meshgrid (x ,y )
678
650
xx , yy = xx .ravel (), yy .ravel ()
679
651
680
- rl_mask = rl .contains_many (xx . ravel () , yy . ravel () )
652
+ rl_mask = rl .contains_many (xx , yy )
681
653
if np .any (rl_mask ):
682
654
index = np .argmax (rl_mask )
683
655
new_lon = xx [index ]
@@ -702,9 +674,9 @@ def interact_with_coastline(self, final=False):
702
674
'moving back to water' % len (on_land ))
703
675
on_land_ID = self .elements .ID [on_land ]
704
676
self .elements .lon [on_land ] = \
705
- np . copy ( self .previous_lon [on_land_ID ])
677
+ self ._elements_previous . lon [on_land_ID ]
706
678
self .elements .lat [on_land ] = \
707
- np . copy ( self .previous_lat [on_land_ID ])
679
+ self ._elements_previous . lat [on_land_ID ]
708
680
self .environment .land_binary_mask [on_land ] = 0
709
681
710
682
def interact_with_seafloor (self ):
@@ -741,10 +713,8 @@ def interact_with_seafloor(self):
741
713
logger .warning ('%s elements hit seafloor, '
742
714
'moving back ' % len (below ))
743
715
below_ID = self .elements .ID [below ]
744
- self .elements .lon [below ] = \
745
- np .copy (self .previous_lon [below_ID ])
746
- self .elements .lat [below ] = \
747
- np .copy (self .previous_lat [below_ID ])
716
+ self .elements .lon [below ] = self ._elements_previous .lon [below_ID ].data
717
+ self .elements .lat [below ] = self ._elements_previous .lat [below_ID ].data
748
718
749
719
@abstractmethod
750
720
def update (self ):
@@ -873,6 +843,7 @@ def release_elements(self):
873
843
'to be seeded: %s, already seeded %s' %
874
844
(len (self .elements_scheduled ), self .num_elements_activated ()))
875
845
if len (self .elements_scheduled ) == 0 :
846
+ self .newly_seeded_IDs = None
876
847
return
877
848
if self .time_step .days >= 0 :
878
849
indices = (self .elements_scheduled_time >= self .time ) & \
@@ -882,9 +853,12 @@ def release_elements(self):
882
853
indices = (self .elements_scheduled_time <= self .time ) & \
883
854
(self .elements_scheduled_time >
884
855
self .time + self .time_step )
885
- self .store_present_positions (self .elements_scheduled .ID [indices ],
886
- self .elements_scheduled .lon [indices ],
887
- self .elements_scheduled .lat [indices ])
856
+
857
+ self .newly_seeded_IDs = self .elements_scheduled .ID [indices ] # may be deactivated if on land
858
+ if self ._elements_previous is not None and 'lon' in self ._elements_previous :
859
+ # store position of newly seeded elements
860
+ self ._elements_previous .lon [self .newly_seeded_IDs ] = self .elements_scheduled .lon [indices ]
861
+ self ._elements_previous .lat [self .newly_seeded_IDs ] = self .elements_scheduled .lat [indices ]
888
862
self .elements_scheduled .move_elements (self .elements , indices )
889
863
self .elements_scheduled_time = self .elements_scheduled_time [~ indices ]
890
864
logger .debug ('Released %i new elements.' % np .sum (indices ))
@@ -915,10 +889,10 @@ def closest_ocean_points(self, lon, lat):
915
889
916
890
if isinstance (land_reader , ShapeReader ):
917
891
# can do this better
918
- is_inside = land_reader .__on_land__ (lon , lat )
919
- lon [is_inside ], lat [is_inside ], _ = land_reader .get_nearest_outside (
920
- lon [is_inside ],
921
- lat [is_inside ],
892
+ land_indices = land_reader .__on_land__ (lon , lat )
893
+ lon [land_indices ], lat [land_indices ], _ = land_reader .get_nearest_outside (
894
+ lon [land_indices ],
895
+ lat [land_indices ],
922
896
buffer_distance = land_reader ._land_kdtree_buffer_distance ,
923
897
)
924
898
@@ -938,11 +912,12 @@ def closest_ocean_points(self, lon, lat):
938
912
time = land_reader .start_time )[0 ]['land_binary_mask' ]
939
913
if land .max () == 0 :
940
914
logger .info ('All points are in ocean' )
941
- return lon , lat
915
+ return lon , lat , None
942
916
logger .info ('Moving %i out of %i points from land to water' %
943
917
(np .sum (land != 0 ), len (lon )))
944
- landlons = lon [land != 0 ]
945
- landlats = lat [land != 0 ]
918
+ land_indices = np .where (land != 0 )[0 ]
919
+ landlons = lon [land_indices ]
920
+ landlats = lat [land_indices ]
946
921
longrid = np .arange (lonmin , lonmax , deltalon )
947
922
latgrid = np .arange (latmin , latmax , deltalat )
948
923
longrid , latgrid = np .meshgrid (longrid , latgrid )
@@ -960,10 +935,10 @@ def closest_ocean_points(self, lon, lat):
960
935
if landgrid .size == 0 :
961
936
# Need to catch this before trying .min() on it...
962
937
logger .warning ('Land grid has zero size, cannot move elements.' )
963
- return lon , lat
938
+ return lon , lat , land_indices
964
939
if landgrid .min () == 1 or np .isnan (landgrid .min ()):
965
940
logger .warning ('No ocean pixels nearby, cannot move elements.' )
966
- return lon , lat
941
+ return lon , lat , land_indices
967
942
968
943
oceangridlons = longrid [landgrid == 0 ]
969
944
oceangridlats = latgrid [landgrid == 0 ]
@@ -972,10 +947,10 @@ def closest_ocean_points(self, lon, lat):
972
947
landpoints = np .dstack ([landlons , landlats ])
973
948
_dist , indices = tree .query (landpoints )
974
949
indices = indices .ravel ()
975
- lon [land != 0 ] = oceangridlons [indices ]
976
- lat [land != 0 ] = oceangridlats [indices ]
950
+ lon [land_indices ] = oceangridlons [indices ]
951
+ lat [land_indices ] = oceangridlats [indices ]
977
952
978
- return lon , lat
953
+ return lon , lat , land_indices
979
954
980
955
@require_mode (mode = Mode .Ready )
981
956
def seed_elements (self ,
@@ -1992,7 +1967,7 @@ def run(self,
1992
1967
if varname == 'ID' or (self .export_variables is not None and
1993
1968
varname not in self .export_variables ):
1994
1969
continue
1995
- attrs = {k :v for k ,v in attrs .copy ().items () if k not in ['seed' , 'default' ]}
1970
+ attrs = {k :v for k ,v in attrs .copy ().items () if k not in ['seed' , 'default' , 'store_previous' ]}
1996
1971
element_vars [varname ] = (dims ,
1997
1972
np .full (shape = shape , fill_value = np .nan , dtype = default_dtype ),
1998
1973
attrs )
@@ -2048,11 +2023,30 @@ def run(self,
2048
2023
('land_binary_mask' in self .required_variables ):
2049
2024
#('land_binary_mask' not in self.fallback_values) and \
2050
2025
self .timer_start ('preparing main loop:moving elements to ocean' )
2051
- self .elements_scheduled .lon , self .elements_scheduled .lat = \
2026
+ self .elements_scheduled .lon , self .elements_scheduled .lat , land_indices = \
2052
2027
self .closest_ocean_points (self .elements_scheduled .lon ,
2053
2028
self .elements_scheduled .lat )
2054
2029
self .timer_end ('preparing main loop:moving elements to ocean' )
2055
2030
2031
+ # TODO: adjust store_previous according to config, later with upcming conditional mechanism
2032
+ if self .get_config ('general:coastline_action' ) in ['previous' , 'stranding' ] or (
2033
+ 'general:coastline_action' in self ._config and
2034
+ self .get_config ('general:coastline_action' ) == 'previous' ):
2035
+ logger .info ('Storing previous position of elements for coastline interaction' )
2036
+ self .elements .variables ['lon' ]['store_previous' ] = True
2037
+ self .elements .variables ['lat' ]['store_previous' ] = True
2038
+
2039
+ # Make Xarray datasets to store environment variables and element properties from previous time step
2040
+ environment_previous = [vn for vn , v in self .required_variables .items ()
2041
+ if v .get ('store_previous' , False ) is True ]
2042
+ elements_previous = [vn for vn , v in self .elements .variables .items ()
2043
+ if v .get ('store_previous' , False ) is True ]
2044
+
2045
+ if len (environment_previous ) > 0 :
2046
+ self ._environment_previous = self .result [[* environment_previous ]].isel (time = 0 , drop = True ).copy (deep = True )
2047
+ if len (elements_previous ) > 0 :
2048
+ self ._elements_previous = self .result [[* elements_previous ]].isel (time = 0 , drop = True ).copy (deep = True )
2049
+
2056
2050
#############################
2057
2051
# Check validity domain
2058
2052
#############################
@@ -2147,9 +2141,8 @@ def run(self,
2147
2141
2148
2142
self .remove_deactivated_elements ()
2149
2143
2150
- # Store location, in case elements shall be moved back
2151
- self .store_present_positions ()
2152
-
2144
+ self .update_previous_state () # Store location, in case elements shall be moved back
2145
+
2153
2146
if self .num_elements_active () > 0 :
2154
2147
########################################
2155
2148
# Calling module specific update method
0 commit comments