-
-
Notifications
You must be signed in to change notification settings - Fork 425
r.mapcalc: Add tests for math functions and operators #6975
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
Open
saurabh12nxf
wants to merge
4
commits into
OSGeo:main
Choose a base branch
from
saurabh12nxf:add-tests-r-mapcalc-functions
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
be94940
Add tests for r.mapcalc functions and operators
saurabh12nxf c186cc5
Changed the author name in the header
saurabh12nxf 967fed9
Applyied changes according to the Anna's feedback
saurabh12nxf 1b7e0b0
r.mapcalc: Test &&&/||| special behavior
saurabh12nxf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,311 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| ############################################################################ | ||
| # | ||
| # MODULE: test_r_mapcalc_functions.py | ||
| # AUTHOR: Saurabh Singh | ||
| # PURPOSE: Test math functions, conditionals, and operators in r.mapcalc | ||
| # COPYRIGHT: (C) 2026 by Saurabh Singh and the GRASS Development Team | ||
| # | ||
| # This program is free software under the GNU General Public | ||
| # License (>=v2). Read the file COPYING that comes with GRASS | ||
| # for details. | ||
| # | ||
| ############################################################################# | ||
|
|
||
| from grass.gunittest.case import TestCase | ||
| from grass.gunittest.main import test | ||
|
|
||
|
|
||
| class TestMathFunctions(TestCase): | ||
| """Test mathematical functions in r.mapcalc""" | ||
|
|
||
| output = "test_output" | ||
| to_remove = [] | ||
|
|
||
| @classmethod | ||
| def setUpClass(cls): | ||
| """Create test environment""" | ||
| cls.use_temp_region() | ||
| cls.runModule("g.region", n=3, s=0, e=3, w=0, res=1) | ||
|
|
||
| @classmethod | ||
| def tearDownClass(cls): | ||
| """Clean up""" | ||
| cls.del_temp_region() | ||
| if cls.to_remove: | ||
| cls.runModule("g.remove", flags="f", type="raster", name=cls.to_remove) | ||
|
|
||
| def test_sqrt(self): | ||
| """Test sqrt() function""" | ||
| expression = f"{self.output} = sqrt(16)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.to_remove.append(self.output) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 4, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_exp(self): | ||
| """Test exp() function""" | ||
| expression = f"{self.output} = exp(0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_log(self): | ||
| """Test log() function""" | ||
| expression = f"{self.output} = log(2.718281828)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_sin(self): | ||
| """Test sin() function""" | ||
| expression = f"{self.output} = sin(0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 0, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_cos(self): | ||
| """Test cos() function""" | ||
| expression = f"{self.output} = cos(0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_abs(self): | ||
| """Test abs() function""" | ||
| expression = f"{self.output} = abs(-5)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 5, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
|
|
||
| class TestConditionals(TestCase): | ||
| """Test conditional statements in r.mapcalc""" | ||
|
|
||
| input_map = "test_input" | ||
| output = "test_output" | ||
| to_remove = [] | ||
|
|
||
| @classmethod | ||
| def setUpClass(cls): | ||
| """Create test data""" | ||
| cls.use_temp_region() | ||
| cls.runModule("g.region", n=3, s=0, e=3, w=0, res=1) | ||
| cls.runModule("r.mapcalc", expression=f"{cls.input_map} = row() * col()") | ||
| cls.to_remove.append(cls.input_map) | ||
|
|
||
| @classmethod | ||
| def tearDownClass(cls): | ||
| """Clean up""" | ||
| cls.del_temp_region() | ||
| if cls.to_remove: | ||
| cls.runModule("g.remove", flags="f", type="raster", name=cls.to_remove) | ||
|
|
||
| def test_if_simple(self): | ||
| """Test simple if() statement""" | ||
| expression = f"{self.output} = if({self.input_map} > 4, 1, 0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.to_remove.append(self.output) | ||
| self.assertRasterFitsUnivar(self.output, {"min": 0, "max": 1}, precision=1e-6) | ||
|
|
||
| def test_if_nested(self): | ||
| """Test nested if() statements""" | ||
| expression = f"{self.output} = if({self.input_map} < 3, 1, if({self.input_map} < 6, 2, 3))" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.to_remove.append(self.output) | ||
| self.assertRasterFitsUnivar(self.output, {"min": 1, "max": 3}, precision=1e-6) | ||
|
|
||
|
|
||
| class TestLogicalOperators(TestCase): | ||
| """Test logical operators in r.mapcalc""" | ||
|
|
||
| input_a = "test_input_a" | ||
| input_b = "test_input_b" | ||
| output = "test_output" | ||
| to_remove = [] | ||
|
|
||
| @classmethod | ||
| def setUpClass(cls): | ||
| """Create test environment with input rasters""" | ||
| cls.use_temp_region() | ||
| cls.runModule("g.region", n=3, s=0, e=3, w=0, res=1) | ||
| cls.runModule("r.mapcalc", expression=f"{cls.input_a} = if(row() > 1, 1, 0)") | ||
| cls.runModule("r.mapcalc", expression=f"{cls.input_b} = if(col() > 1, 1, 0)") | ||
| cls.to_remove.extend([cls.input_a, cls.input_b]) | ||
|
|
||
| @classmethod | ||
| def tearDownClass(cls): | ||
| """Clean up""" | ||
| cls.del_temp_region() | ||
| if cls.to_remove: | ||
| cls.runModule("g.remove", flags="f", type="raster", name=cls.to_remove) | ||
|
|
||
| def test_and_operator(self): | ||
| """Test && (AND) operator""" | ||
| expression = f"{self.output} = if(1 && 1, 1, 0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.to_remove.append(self.output) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_or_operator(self): | ||
| """Test || (OR) operator""" | ||
| expression = f"{self.output} = if(0 || 1, 1, 0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_not_operator(self): | ||
| """Test ! (NOT) operator""" | ||
| expression = f"{self.output} = if(!0, 1, 0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_and_and_and_operator_special_behavior(self): | ||
| """Test &&& operator handles NULL differently than && | ||
|
|
||
| Regular && returns NULL if any operand is NULL. | ||
| Special &&& returns 0 if one operand is 0, even if the other is NULL. | ||
| """ | ||
| # Create NULL and Zero maps | ||
| null_map = "test_null_and" | ||
| zero_map = "test_zero" | ||
| self.runModule("r.mapcalc", expression=f"{null_map} = null()") | ||
| self.runModule("r.mapcalc", expression=f"{zero_map} = 0") | ||
| self.to_remove.extend([null_map, zero_map]) | ||
|
|
||
| # Test &&& (Special) | ||
| # null &&& 0 should be 0 | ||
| expression = f"{self.output} = {null_map} &&& {zero_map}" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.to_remove.append(self.output) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 0, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_or_or_or_operator_special_behavior(self): | ||
| """Test ||| operator handles NULL differently than || | ||
|
|
||
| Regular || returns NULL if any operand is NULL. | ||
| Special ||| returns 1 if one operand is 1, even if the other is NULL. | ||
| """ | ||
| # Create NULL and One maps | ||
| null_map = "test_null_or" | ||
| one_map = "test_one" | ||
| self.runModule("r.mapcalc", expression=f"{null_map} = null()") | ||
| self.runModule("r.mapcalc", expression=f"{one_map} = 1") | ||
| self.to_remove.extend([null_map, one_map]) | ||
|
|
||
| # Test ||| (Special) | ||
| # null ||| 1 should be 1 | ||
| expression = f"{self.output} = {null_map} ||| {one_map}" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.to_remove.append(self.output) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
|
|
||
| class TestComparisonOperators(TestCase): | ||
| """Test comparison operators in r.mapcalc""" | ||
|
|
||
| output = "test_output" | ||
| to_remove = [] | ||
|
|
||
| @classmethod | ||
| def setUpClass(cls): | ||
| """Create test environment""" | ||
| cls.use_temp_region() | ||
| cls.runModule("g.region", n=3, s=0, e=3, w=0, res=1) | ||
|
|
||
| @classmethod | ||
| def tearDownClass(cls): | ||
| """Clean up""" | ||
| cls.del_temp_region() | ||
| if cls.to_remove: | ||
| cls.runModule("g.remove", flags="f", type="raster", name=cls.to_remove) | ||
|
|
||
| def test_greater_than(self): | ||
| """Test > operator""" | ||
| expression = f"{self.output} = if(5 > 3, 1, 0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.to_remove.append(self.output) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_less_than(self): | ||
| """Test < operator""" | ||
| expression = f"{self.output} = if(3 < 5, 1, 0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_equal(self): | ||
| """Test == operator""" | ||
| expression = f"{self.output} = if(5 == 5, 1, 0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_not_equal(self): | ||
| """Test != operator""" | ||
| expression = f"{self.output} = if(5 != 3, 1, 0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
|
|
||
| class TestNullHandling(TestCase): | ||
| """Test null value handling in r.mapcalc""" | ||
|
|
||
| null_map = "test_null" | ||
| output = "test_output" | ||
| to_remove = [] | ||
|
|
||
| @classmethod | ||
| def setUpClass(cls): | ||
| """Create test data with nulls""" | ||
| cls.use_temp_region() | ||
| cls.runModule("g.region", n=3, s=0, e=3, w=0, res=1) | ||
| # Create map with null values | ||
| cls.runModule("r.mapcalc", expression=f"{cls.null_map} = null()") | ||
| cls.to_remove.append(cls.null_map) | ||
|
|
||
| @classmethod | ||
| def tearDownClass(cls): | ||
| """Clean up""" | ||
| cls.del_temp_region() | ||
| if cls.to_remove: | ||
| cls.runModule("g.remove", flags="f", type="raster", name=cls.to_remove) | ||
|
|
||
| def test_isnull(self): | ||
| """Test isnull() function""" | ||
| expression = f"{self.output} = if(isnull({self.null_map}), 1, 0)" | ||
| self.assertModule("r.mapcalc", expression=expression, overwrite=True) | ||
| self.to_remove.append(self.output) | ||
| self.assertRasterFitsUnivar( | ||
| self.output, {"mean": 1, "range": 0}, precision=1e-6 | ||
| ) | ||
|
|
||
| def test_null_creation(self): | ||
| """Test null() creates a null raster""" | ||
| self.assertRasterFitsUnivar(self.null_map, {"n": 0}, precision=1e-6) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| test() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Test the &&& and ||| operators, they are more tricky. Create suitable input rasters for that.
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.
Read the r.mapcalc documentation, the test as you have it now is not actually testing the special behavior of the &&& and ||| operators.