fix a few bugs introduced in merge and update tests for #1373

This commit is contained in:
Emily Soth 2023-08-17 15:13:22 -07:00
parent a805df880d
commit 0e0c7b1bae
12 changed files with 53 additions and 178 deletions

View File

@ -1521,9 +1521,8 @@ def _validate_same_id_lengths(table_path):
table_path (string): path to a csv table that has at least
the field 'id'
Raises:
ValueError if any of the fields in 'id' and 'type' don't match between
tables.
Return:
string message if IDs are too long
"""
predictor_df = utils.read_csv_to_dataframe(
@ -1552,12 +1551,8 @@ def _validate_same_ids_and_types(
at least the fields 'id' and 'type'
Returns:
None
Raises:
ValueError if any of the fields in 'id' and 'type' don't match between
tables.
string message if any of the fields in 'id' and 'type' don't match
between tables.
"""
predictor_df = utils.read_csv_to_dataframe(
predictor_table_path, MODEL_SPEC['args']['predictor_table_path'])
@ -1571,8 +1566,8 @@ def _validate_same_ids_and_types(
scenario_predictor_pairs = set([
(p_id, row['type']) for p_id, row in scenario_predictor_df.iterrows()])
if predictor_pairs != scenario_predictor_pairs:
return (f'table pairs unequal. predictor: {predictor_table_pairs} '
f'scenario: {scenario_predictor_table_pairs}')
return (f'table pairs unequal. predictor: {predictor_pairs} '
f'scenario: {scenario_predictor_pairs}')
def _validate_same_projection(base_vector_path, table_path):
@ -1584,12 +1579,8 @@ def _validate_same_projection(base_vector_path, table_path):
the field 'path'
Returns:
None
Raises:
ValueError if the projections in each of the GIS types in the table
string message if the projections in each of the GIS types in the table
are not identical to the projection in base_vector_path
"""
# This will load the table as a list of paths which we can iterate through
# without bothering the rest of the table structure
@ -1620,16 +1611,12 @@ def _validate_same_projection(base_vector_path, table_path):
else:
vector = gdal.OpenEx(path, gdal.OF_VECTOR)
if vector is None:
raise ValueError(f"{path} did not load")
return f"{path} did not load"
layer = vector.GetLayer()
ref = osr.SpatialReference(layer.GetSpatialRef().ExportToWkt())
layer = None
vector = None
if not base_ref.IsSame(ref):
LOGGER.warning(
f"{path} might have a different projection than the base AOI\n"
f"base:{base_ref.ExportToPrettyWkt()}\n"
f"current:{ref.ExportToPrettyWkt()}")
invalid_projections = True
if invalid_projections:
return ("One or more of the projections in the table did not match "
@ -1644,11 +1631,8 @@ def _validate_predictor_types(table_path):
the field 'type'
Returns:
None
Raises:
ValueError if any value in the ``type`` column does not match a valid
type, ignoring leading/trailing whitespace.
string message if any value in the ``type`` column does not match a
valid type, ignoring leading/trailing whitespace.
"""
df = utils.read_csv_to_dataframe(
table_path, MODEL_SPEC['args']['predictor_table_path'])
@ -1707,8 +1691,9 @@ def validate(args, limit_to=None):
sufficient_valid_keys = (validation.get_sufficient_keys(args) -
validation.get_invalid_keys(validation_messages))
validation_tuples = []
if 'predictor_table_path' in sufficient_valid_keys:
validation_tuples = [
validation_tuples += [
(_validate_same_id_lengths, ['predictor_table_path']),
(_validate_same_projection, ['aoi_path', 'predictor_table_path']),
(_validate_predictor_types, ['predictor_table_path'])]
@ -1727,8 +1712,8 @@ def validate(args, limit_to=None):
if 'start_year' in sufficient_valid_keys and 'end_year' in sufficient_valid_keys:
if int(args['end_year']) < int(args['start_year']):
validation_messages.append(
validation_messages.append((
['start_year', 'end_year'],
"Start year must be less than or equal to end year.")
"Start year must be less than or equal to end year."))
return validation_messages

View File

@ -791,7 +791,7 @@ def _calculate_valuation(visibility_path, viewpoint, weight,
weight * visibility[valid_pixels]))
return valuation
elif valuation_method == 'exponential':
else: # exponential
def _valuation(distance, visibility):
valid_pixels = (visibility == 1)

View File

@ -2758,22 +2758,26 @@ def validate(args, limit_to=None):
Returns:
A list of tuples where tuple[0] is an iterable of keys that the error
message applies to and tuple[1] is the str validation warning.
"""
validation_warnings = validation.validate(args, MODEL_SPEC['args'],
MODEL_SPEC['args_with_spatial_overlap'])
invalid_keys = validation.get_invalid_keys(validation_warnings)
if ('wind_schedule' not in invalid_keys and
'global_wind_parameters_path' not in invalid_keys):
sufficient_keys = validation.get_sufficient_keys(args)
valid_sufficient_keys = sufficient_keys - invalid_keys
if ('wind_schedule' in valid_sufficient_keys and
'global_wind_parameters_path' in valid_sufficient_keys):
year_count = len(
utils.read_csv_to_dataframe(args['wind_schedule'])['year'])
utils.read_csv_to_dataframe(
args['wind_schedule'], MODEL_SPEC['args']['wind_schedule']))
time = int(_read_csv_wind_parameters(
args['global_wind_parameters_path'], valuation_global_params
args['global_wind_parameters_path'], ['time_period']
)['time_period'])
if year_count != time + 1:
validation_warnings.append((
['wind_schedule']
['wind_schedule'],
"The 'time' argument in the Global Wind Energy Parameters "
"file must equal the number of years provided in the price"
" table.")
"file must equal the number of years provided in the price "
"table."))
return validation_warnings

View File

@ -203,34 +203,6 @@ class ForestCarbonEdgeTests(unittest.TestCase):
actual_message = str(cm.exception)
self.assertTrue(expected_message in actual_message, actual_message)
def test_missing_aoi(self):
"""Forest carbon edge: ensure missing AOI causes exception."""
from natcap.invest import forest_carbon_edge_effect
args = {
'biomass_to_carbon_conversion_factor': '0.47',
'biophysical_table_path': os.path.join(
REGRESSION_DATA, 'input',
'no_forest_edge_carbon_lu_table_bad_pool_value.csv'),
'compute_forest_edge_effects': False,
'lulc_raster_path': os.path.join(
REGRESSION_DATA, 'input', 'small_lulc.tif'),
'n_nearest_model_points': 1,
'pools_to_calculate': 'all',
'results_suffix': 'small_no_edge_effect',
'tropical_forest_edge_carbon_model_vector_path': os.path.join(
REGRESSION_DATA, 'input', 'core_data',
'forest_carbon_edge_regression_model_parameters.shp'),
'workspace_dir': self.workspace_dir,
'n_workers': -1
}
args['aoi_vector_path'] = os.path.join(
'path', 'to', 'nonexistant', 'aoi.shp')
with self.assertRaises(ValueError) as cm:
forest_carbon_edge_effect.execute(args)
expected_message = 'Unable to open aoi at:'
actual_message = str(cm.exception)
self.assertTrue(expected_message in actual_message, actual_message)
def test_carbon_nodata_lulc(self):
"""Forest Carbon Edge: ensure nodata lulc raster cause exception."""
from natcap.invest import forest_carbon_edge_effect

View File

@ -1476,10 +1476,3 @@ class HRAModelTests(unittest.TestCase):
str(cm.exception))
self.assertIn("Missing from criteria table: transportation",
str(cm.exception))
args['risk_eq'] = 'some other risk eq'
with self.assertRaises(ValueError) as cm:
hra.execute(args)
self.assertIn("must be either 'Multiplicative' or 'Euclidean'",
str(cm.exception))

View File

@ -102,7 +102,7 @@ class NDRTests(unittest.TestCase):
normalized_array, expected_array, rtol=0, atol=1e-6)
def test_missing_headers(self):
"""NDR biphysical headers missing should raise a ValueError."""
"""NDR biphysical headers missing should return validation message."""
from natcap.invest.ndr import ndr
# use predefined directory so test can clean up files during teardown
@ -110,8 +110,8 @@ class NDRTests(unittest.TestCase):
# make args explicit that this is a base run of SWY
args['biophysical_table_path'] = os.path.join(
REGRESSION_DATA, 'input', 'biophysical_table_missing_headers.csv')
with self.assertRaises(ValueError):
ndr.execute(args)
validation_messages = ndr.validate(args)
self.assertEqual(len(validation_messages), 1)
def test_crit_len_0(self):
"""NDR test case where crit len is 0 in biophysical table."""
@ -182,7 +182,7 @@ class NDRTests(unittest.TestCase):
in actual_message)
def test_no_nutrient_selected(self):
"""NDR no nutrient selected should raise a ValueError."""
"""NDR no nutrient selected should return a validation message."""
from natcap.invest.ndr import ndr
# use predefined directory so test can clean up files during teardown
@ -190,8 +190,8 @@ class NDRTests(unittest.TestCase):
# make args explicit that this is a base run of SWY
args['calc_n'] = False
args['calc_p'] = False
with self.assertRaises(ValueError):
ndr.execute(args)
validation_messages = ndr.validate(args)
self.assertEqual(len(validation_messages), 1)
def test_base_regression(self):
"""NDR base regression test on sample data.

View File

@ -697,9 +697,8 @@ class RecreationRegressionTests(unittest.TestCase):
table_path = os.path.join(
SAMPLE_DATA, 'predictors_data_missing.csv')
with self.assertRaises(ValueError):
recmodel_client._validate_same_projection(
response_vector_path, table_path)
self.assertIsNotNone(recmodel_client._validate_same_projection(
response_vector_path, table_path))
def test_data_different_projection(self):
"""Recreation raise exception if data in different projection."""
@ -709,9 +708,8 @@ class RecreationRegressionTests(unittest.TestCase):
table_path = os.path.join(
SAMPLE_DATA, 'predictors_wrong_projection.csv')
with self.assertRaises(ValueError):
recmodel_client._validate_same_projection(
response_vector_path, table_path)
self.assertIsNotNone(recmodel_client._validate_same_projection(
response_vector_path, table_path))
def test_different_tables(self):
"""Recreation exception if scenario ids different than predictor."""
@ -721,10 +719,9 @@ class RecreationRegressionTests(unittest.TestCase):
SAMPLE_DATA, 'predictors_data_missing.csv')
scenario_table_path = os.path.join(
SAMPLE_DATA, 'predictors_wrong_projection.csv')
with self.assertRaises(ValueError):
self.assertIsNotNone(
recmodel_client._validate_same_ids_and_types(
base_table_path, scenario_table_path)
base_table_path, scenario_table_path))
def test_delay_op(self):
"""Recreation coverage of delay op function."""
@ -833,7 +830,6 @@ class RecreationRegressionTests(unittest.TestCase):
with open(predictor_target_path, 'r') as file:
data = json.load(file)
print(data)
actual_value = list(data.values())[0]
expected_value = 1
self.assertEqual(actual_value, expected_value)
@ -984,9 +980,8 @@ class RecreationRegressionTests(unittest.TestCase):
'results_suffix': '',
'workspace_dir': self.workspace_dir,
}
with self.assertRaises(ValueError):
recmodel_client.execute(args)
msgs = recmodel_client.validate(args)
self.assertIn('more than 10 characters long', msgs[0][1])
def test_existing_output_shapefiles(self):
"""Recreation grid test when output files need to be overwritten."""
@ -1125,9 +1120,9 @@ class RecreationRegressionTests(unittest.TestCase):
SAMPLE_DATA, 'predictors_scenario.csv'),
'workspace_dir': self.workspace_dir,
}
with self.assertRaises(ValueError):
recmodel_client.execute(args)
msgs = recmodel_client.validate(args)
self.assertEqual(
'Start year must be less than or equal to end year.', msgs[0][1])
def test_bad_grid_type(self):
"""Recreation ensure that bad grid type raises ValueError."""
@ -1341,11 +1336,8 @@ class RecreationValidationTests(unittest.TestCase):
'predictor_table_path': bad_table_path,
'workspace_dir': self.workspace_dir,
}
with self.assertRaises(ValueError) as cm:
recmodel_client.execute(args)
self.assertTrue('The table contains invalid type value(s)' in
str(cm.exception))
msgs = recmodel_client.validate(args)
self.assertIn('The table contains invalid type value(s)', msgs[0][1])
def _assert_regression_results_eq(

View File

@ -59,23 +59,6 @@ class RouteDEMTests(unittest.TestCase):
dem_raster.SetGeoTransform(dem_geotransform)
dem_raster = None
def test_routedem_invalid_algorithm(self):
"""RouteDEM: fail when the algorithm isn't recognized."""
from natcap.invest import routedem
args = {
'workspace_dir': self.workspace_dir,
'algorithm': 'invalid',
'dem_path': os.path.join(self.workspace_dir, 'dem.tif'),
'results_suffix': 'foo',
'calculate_flow_direction': True,
}
RouteDEMTests._make_dem(args['dem_path'])
with self.assertRaises(RuntimeError) as cm:
routedem.execute(args)
self.assertTrue('Invalid algorithm specified' in str(cm.exception))
def test_routedem_no_options_default_band(self):
"""RouteDEM: default to band 1 when not specified."""
from natcap.invest import routedem

View File

@ -225,38 +225,6 @@ class ScenicQualityTests(unittest.TestCase):
quality_matrix,
rtol=0, atol=1e-6)
def test_invalid_valuation_function(self):
"""SQ: model raises exception with invalid valuation function."""
from natcap.invest.scenic_quality import scenic_quality
dem_path = os.path.join(self.workspace_dir, 'dem.tif')
ScenicQualityTests.create_dem(dem_path)
viewpoints_path = os.path.join(self.workspace_dir,
'viewpoints.geojson')
ScenicQualityTests.create_viewpoints(viewpoints_path)
aoi_path = os.path.join(self.workspace_dir, 'aoi.geojson')
ScenicQualityTests.create_aoi(aoi_path)
args = {
'workspace_dir': os.path.join(self.workspace_dir, 'workspace'),
'results_suffix': 'foo',
'aoi_path': aoi_path,
'structure_path': viewpoints_path,
'dem_path': dem_path,
'refraction': 0.13,
'do_valuation': True,
'valuation_function': 'INVALID FUNCTION',
'a_coef': 1,
'b_coef': 0,
'max_valuation_radius': 10.0,
'n_workers': -1,
}
with self.assertRaises(ValueError):
scenic_quality.execute(args)
def test_error_invalid_viewpoints(self):
"""SQ: error when no valid viewpoints.

View File

@ -297,13 +297,6 @@ class UFRMTests(unittest.TestCase):
self.assertEqual(len(aoi_damage_dict), 1)
numpy.testing.assert_allclose(aoi_damage_dict[0], 5645.787282992962)
def test_ufrm_invalid_validation(self):
"""UFRM: assert validation error on bad args."""
from natcap.invest import urban_flood_risk_mitigation
with self.assertRaises(ValueError):
urban_flood_risk_mitigation.execute({})
def test_validate(self):
"""UFRM: test validate function."""
from natcap.invest import urban_flood_risk_mitigation

View File

@ -807,22 +807,6 @@ class UNATests(unittest.TestCase):
[polygon_1, polygon_2, polygon_3], vector_path, wkt, 'GeoJSON')
self.assertTrue(urban_nature_access._geometries_overlap(vector_path))
def test_invalid_search_radius_mode(self):
"""UNA: Assert an exception when invalid radius mode provided."""
from natcap.invest import urban_nature_access
args = _build_model_args(self.workspace_dir)
args['search_radius_mode'] = 'some invalid mode'
with self.assertRaises(ValueError) as cm:
urban_nature_access.execute(args)
self.assertIn('Invalid search radius mode provided', str(cm.exception))
for mode_suffix in ('UNIFORM', 'URBAN_NATURE', 'POP_GROUP'):
valid_mode_string = getattr(urban_nature_access,
f'RADIUS_OPT_{mode_suffix}')
self.assertIn(valid_mode_string, str(cm.exception))
def test_square_pixels(self):
"""UNA: Assert we can make square pixels as expected."""
from natcap.invest import urban_nature_access

View File

@ -723,7 +723,7 @@ class WindEnergyRegressionTests(unittest.TestCase):
os.path.join(REGRESSION_DATA, 'priceval', vector_path))
def test_field_error_missing_bio_param(self):
"""WindEnergy: test that ValueError raised when missing bio param."""
"""WindEnergy: test that validation catches missing bio param."""
from natcap.invest import wind_energy
# for testing raised exceptions, running on a set of data that was
@ -749,7 +749,7 @@ class WindEnergyRegressionTests(unittest.TestCase):
}
# creating a stand in turbine parameter csv file that is missing
# the 'cut_out_wspd' entry. This should raise the exception
# the 'cut_out_wspd' entry.
tmp, file_path = tempfile.mkstemp(
suffix='.csv', dir=args['workspace_dir'])
os.close(tmp)
@ -760,10 +760,11 @@ class WindEnergyRegressionTests(unittest.TestCase):
_create_vertical_csv(data, file_path)
args['turbine_parameters_path'] = file_path
self.assertRaises(ValueError, wind_energy.execute, args)
validation_messages = wind_energy.validate(args)
self.assertEqual(len(validation_messages), 1)
def test_time_period_exception(self):
"""WindEnergy: raise ValueError if 'time' and 'wind_sched' differ."""
"""WindEnergy: validation message if 'time' and 'wind_sched' differ."""
from natcap.invest import wind_energy
# for testing raised exceptions, running on a set of data that was
@ -815,8 +816,8 @@ class WindEnergyRegressionTests(unittest.TestCase):
}
_create_vertical_csv(data, file_path)
args['global_wind_parameters_path'] = file_path
self.assertRaises(ValueError, wind_energy.execute, args)
validation_messages = wind_energy.validate(args)
self.assertEqual(len(validation_messages), 1)
def test_clip_vector_value_error(self):
"""WindEnergy: Test AOI doesn't intersect Wind Data points."""