Repository: IMMM-SFA/population_gravity

Issue:
Raise key variables to Model class level
Raise the following additional variables to the Model class level so that they may be modified by the user:

- `kernel_distance`:  Distance kernel in meters; default 100,000 meters
 

Discussion:

Patch:
diff --git a/README.md b/README.md
index e9c3adf..f489290 100755
--- a/README.md
+++ b/README.md
@@ -63,6 +63,7 @@ See examples below for how to pass into the `Model` class
 | `calibration_urban_year_two_raster` | string | Only used for running calibration.  Full path with file name and extension to a raster containing urban population counts for each 1 km grid cell for year two of the calibration. |
 | `calibration_rural_year_one_raster` | string | Only used for running calibration.  Full path with file name and extension to a raster containing rural population counts for each 1 km grid cell for year one of the calibration. |
 | `calibration_rural_year_two_raster` | string | Only used for running calibration.  Full path with file name and extension to a raster containing rural population counts for each 1 km grid cell for year two of the calibration. |
+| `kernel_distance_meters` | float | Distance kernel in meters; default 100,000 meters. |
 
 ### Variable arguments
 Users can update variable argument values after model initialization; this includes updating values between time steps (see **Example 3**).  The following are variable arguments:
@@ -72,6 +73,7 @@ Users can update variable argument values after model initialization; this inclu
 - `beta_rural`
 - `urban_pop_proj_n`
 - `rural_pop_proj_n`
+- `kernel_distance_meters`
 
 ### YAML configuration file option (e.g., config.yml)
 Arguments can be passed into the `Model` class using a YAML configuration file as well (see **Example 1**):
@@ -89,6 +91,7 @@ alpha_urban: 2.0
 alpha_rural: 0.08
 beta_urban: 1.78
 beta_rural: 1.42
+kernel_distance_meters: 100000
 scenario: 'SSP2'
 state_name: 'vermont'
 historic_base_year: 2010
@@ -137,6 +140,7 @@ run = Model(grid_coordinates_file='<Full path with file name and extension to th
             alpha_rural=0.08,
             beta_urban=1.78,
             beta_rural=1.42,
+            kernel_distance_meters=100000,
             scenario='SSP2', # shared socioeconomic pathway abbreviation
             state_name='vermont',
             historic_base_year=2010,
@@ -162,6 +166,7 @@ run = Model(grid_coordinates_file='<Full path with file name and extension to th
             alpha_rural=0.08,
             beta_urban=1.78,
             beta_rural=1.42,
+            kernel_distance_meters=100000,
             scenario='SSP2', # shared socioeconomic pathway abbreviation
             state_name='vermont',
             historic_base_year=2010,
diff --git a/population_gravity/main.py b/population_gravity/main.py
index 92dda88..0cf0c7f 100755
--- a/population_gravity/main.py
+++ b/population_gravity/main.py
@@ -126,6 +126,8 @@ class Model:
                                                 extension to a raster containing rural population counts for each 1 km
                                                 grid cell for year two of the  calibration.
 
+    :param kernel_distance_meters:              float. Distance kernel in meters; default 100,000 meters.
+
     """
 
     def __init__(self, config_file=None, grid_coordinates_file=None, historical_suitability_raster=None,
@@ -134,7 +136,8 @@ class Model:
                  alpha_rural=None, beta_rural=None, scenario=None, state_name=None, historic_base_year=None,
                  projection_start_year=None,  projection_end_year=None, time_step=None, rural_pop_proj_n=None,
                  urban_pop_proj_n=None, calibration_urban_year_one_raster=None, calibration_urban_year_two_raster=None,
-                 calibration_rural_year_one_raster=None, calibration_rural_year_two_raster=None):
+                 calibration_rural_year_one_raster=None, calibration_rural_year_two_raster=None,
+                 kernel_distance_meters=None):
 
         # get current time
         self.date_time_string = datetime.datetime.now().strftime('%Y-%m-%d_%Hh%Mm%Ss')
@@ -163,7 +166,8 @@ class Model:
                               calibration_urban_year_one_raster=calibration_urban_year_one_raster,
                               calibration_urban_year_two_raster=calibration_urban_year_two_raster,
                               calibration_rural_year_one_raster=calibration_rural_year_one_raster,
-                              calibration_rural_year_two_raster=calibration_rural_year_two_raster)
+                              calibration_rural_year_two_raster=calibration_rural_year_two_raster,
+                              kernel_distance_meters=kernel_distance_meters)
 
         # expose key variables that we want the user to have non-nested access to
         self.alpha_urban = self.cfg.alpha_urban
@@ -172,6 +176,7 @@ class Model:
         self.beta_rural = self.cfg.beta_rural
         self.rural_pop_proj_n = self.cfg.rural_pop_proj_n
         self.urban_pop_proj_n = self.cfg.urban_pop_proj_n
+        self.kernel_distance_meters = self.cfg.kernel_distance_meters
 
         # logfile path
         self.logfile = os.path.join(self.cfg.output_directory, 'logfile_{}_{}_{}.log'.format(self.cfg.scenario,
@@ -235,6 +240,11 @@ class Model:
         logging.info("\tprojected_population_file = {}".format(self.cfg.projected_population_file))
         logging.info("\tscenario = {}".format(self.cfg.scenario))
         logging.info("\toutput_directory = {}".format(self.cfg.output_directory))
+        logging.info("\tkernel_distance_meters = {}".format(self.cfg.kernel_distance_meters))
+        logging.info("\talpha_urban = {}".format(self.cfg.alpha_urban))
+        logging.info("\talpha_rural = {}".format(self.cfg.alpha_rural))
+        logging.info("\tbeta_urban = {}".format(self.cfg.beta_urban))
+        logging.info("\tbeta_rural = {}".format(self.cfg.beta_rural))
 
     def build_step_generator(self):
         """Build step generator."""
@@ -242,7 +252,7 @@ class Model:
         for step in self.cfg.steps:
 
             yield ProcessStep(self.cfg, step, self.alpha_urban, self.beta_urban, self.alpha_rural, self.beta_rural,
-                              self.rural_pop_proj_n, self.urban_pop_proj_n)
+                              self.rural_pop_proj_n, self.urban_pop_proj_n, self.kernel_distance_meters)
 
     def advance_step(self):
         """Advance to next time step.
diff --git a/population_gravity/process_step.py b/population_gravity/process_step.py
index dd95647..4cb0c96 100644
--- a/population_gravity/process_step.py
+++ b/population_gravity/process_step.py
@@ -28,7 +28,8 @@ class ProcessStep:
 
     """
 
-    def __init__(self, cfg, yr, alpha_urban, beta_urban, alpha_rural, beta_rural, rural_pop_proj_n, urban_pop_proj_n):
+    def __init__(self, cfg, yr, alpha_urban, beta_urban, alpha_rural, beta_rural, rural_pop_proj_n, urban_pop_proj_n,
+                 kernel_distance_meters):
 
         # start time
         td = time.time()
@@ -46,7 +47,7 @@ class ProcessStep:
             # run downscaling
             pop_projection(self.cfg, self.cfg.historical_urban_pop_raster, self.cfg.historical_rural_pop_raster,
                            alpha_urban, beta_urban, alpha_rural, beta_rural, rural_pop_proj_n, urban_pop_proj_n,
-                           self.yr)
+                           self.yr, kernel_distance_meters)
 
         else:
 
@@ -78,7 +79,7 @@ class ProcessStep:
             self.mask_raster(rural_mask_file, rural_mosaic, target_window)
 
             pop_projection(self.cfg, urban_mask_file, rural_mask_file, alpha_urban, beta_urban, alpha_rural, beta_rural,
-                           rural_pop_proj_n, urban_pop_proj_n, self.yr)
+                           rural_pop_proj_n, urban_pop_proj_n, self.yr, kernel_distance_meters)
 
         logging.info("Downscaling for year {} completed in {} minutes.".format(yr, (time.time() - td) / 60))
 
diff --git a/population_gravity/read_config.py b/population_gravity/read_config.py
index 728e1ee..d091ae8 100755
--- a/population_gravity/read_config.py
+++ b/population_gravity/read_config.py
@@ -126,6 +126,8 @@ class ReadConfig:
                                                 extension to a raster containing rural population counts for each 1 km
                                                 grid cell for year two of the  calibration.
 
+    :param kernel_distance_meters:              float. Distance kernel in meters; default 100,000 meters.
+
     """
 
     def __init__(self, config_file=None, grid_coordinates_file=None, historical_suitability_raster=None,
@@ -134,7 +136,8 @@ class ReadConfig:
                  alpha_rural=None, beta_rural=None, scenario=None, state_name=None, historic_base_year=None,
                  projection_start_year=None,  projection_end_year=None, time_step=None, rural_pop_proj_n=None,
                  urban_pop_proj_n=None, calibration_urban_year_one_raster=None, calibration_urban_year_two_raster=None,
-                 calibration_rural_year_one_raster=None, calibration_rural_year_two_raster=None):
+                 calibration_rural_year_one_raster=None, calibration_rural_year_two_raster=None,
+                 kernel_distance_meters=None):
 
         if config_file is None:
 
@@ -157,6 +160,7 @@ class ReadConfig:
             self.time_step = time_step
             self.rural_pop_proj_n = rural_pop_proj_n
             self.urban_pop_proj_n = urban_pop_proj_n
+            self.kernel_distance_meters = kernel_distance_meters
 
             # specific to calibration run
             self.calibration_urban_year_one_raster = calibration_urban_year_one_raster
@@ -188,6 +192,7 @@ class ReadConfig:
             self.time_step = self.validate_key(cfg, 'time_step')
             self.rural_pop_proj_n = self.validate_key(cfg, 'rural_pop_proj_n')
             self.urban_pop_proj_n = self.validate_key(cfg, 'urban_pop_proj_n')
+            self.kernel_distance_meters = self.validate_key(cfg, 'kernel_distance_meters')
 
             # specific to calibration run
             self.calibration_urban_year_one_raster = self.validate_key(cfg, 'calibration_urban_year_one_raster')


Test patch:
diff --git a/population_gravity/tests/test_projection_outputs.py b/population_gravity/tests/test_projection_outputs.py
index 075f2bc..de0136f 100644
--- a/population_gravity/tests/test_projection_outputs.py
+++ b/population_gravity/tests/test_projection_outputs.py
@@ -66,6 +66,7 @@ class TestProjectedOutputs(unittest.TestCase):
                     alpha_rural=0.0750326293181678,
                     beta_urban=1.77529986067379,
                     beta_rural=1.42410799449511,
+                    kernel_distance_meters=100000,
                     scenario=TestProjectedOutputs.SCENARIO,
                     state_name=TestProjectedOutputs.STATE_NAME,
                     historic_base_year=2010,