From cb3d34f231b698f794dda1a70081452a45f03391 Mon Sep 17 00:00:00 2001 From: Oakley Brunt Date: Thu, 26 Jun 2025 13:53:11 +0100 Subject: [PATCH 1/8] #115 Add get_perfectly_nested_loops function plus tests --- psytran/loop.py | 30 ++++++++++++++++++++++++++++++ test/code_snippets.py | 24 ++++++++++++++++++++++++ test/test_loop.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/psytran/loop.py b/psytran/loop.py index 0977a3b..9cd83e7 100644 --- a/psytran/loop.py +++ b/psytran/loop.py @@ -216,3 +216,33 @@ def is_parallelisable(loop): :rtype: :py:class:`bool` """ return loop.independent_iterations() + + +def get_perfectly_nested_loops(schedule): + """ + Finds the topmost level of all perfectly nested loop structures within a + specified schedule and retruns them as a list. + + :arg schedule: the schedule to search. + :type schedule: :py:class:`Schedule` + + :returns loops: all loops that are the topmost of a perfectly nested + structure. + :rtype loops: list[:py:class:`Loop`] + """ + perfectly_nested_loops = [] + + # Test all loops in schedule to see if they are perfectly nested + for loop in schedule.walk(nodes.Loop): + if is_perfectly_nested(loop): + perfectly_nested_loops.append(loop) + + # Check all loops that are perfectly nested to see if they are a descendent + # of another perfectly nested loop and remove them if they are + for top_loop in perfectly_nested_loops: + for bottom_loop in reversed(perfectly_nested_loops): + if bottom_loop in get_descendents(top_loop): + perfectly_nested_loops.remove(bottom_loop) + + # return list of perfectly nested loops + return perfectly_nested_loops \ No newline at end of file diff --git a/test/code_snippets.py b/test/code_snippets.py index ad7c0cf..3849961 100644 --- a/test/code_snippets.py +++ b/test/code_snippets.py @@ -437,6 +437,30 @@ END PROGRAM test """ +perfect_nested_loop_siblings = """ + PROGRAM test + REAL :: a(10,10) + INTEGER :: i + INTEGER :: j + INTEGER :: k + INTEGER :: l + INTEGER :: m + + DO i = 1, 10 + DO j = 1, 10 + DO k = 1, 10 + a(i,j,k) = 0 + END DO + END DO + DO l = 1, 10 + DO m = 1, 10 + a(i,l,m) = 0 + END DO + END DO + END DO + END PROGRAM test + """ + array_assignment_1d = """ PROGRAM test REAL :: a(10) diff --git a/test/test_loop.py b/test/test_loop.py index 13a5001..6ba0873 100644 --- a/test/test_loop.py +++ b/test/test_loop.py @@ -20,6 +20,7 @@ is_parallelisable, is_perfectly_nested, is_simple_loop, + get_perfectly_nested_loops, ) perfectly_nested_loop = { @@ -257,3 +258,30 @@ def test_is_not_independent_triple_subloop(fortran_reader): assert is_perfectly_nested(loops[1]) assert not is_independent(loops[1]) assert is_independent(loops[2]) + + +def test_get_perfectly_nested_sibling_loops(fortran_reader): + """ + Test that :func:`get_perfectly_nested_loops` correctly identifies perfectly + nested looping structures even when they are siblings. + """ + schedule = get_schedule(fortran_reader, cs.perfect_nested_loop_siblings) + loops = get_perfectly_nested_loops(schedule) + assert len(loops) == 2 + assert loops[0].variable.name == "j" + assert loops[1].variable.name == "l" + + +def test_get_perfectly_nested_loop_top_level(fortran_reader): + """ + Test that :func:`get_perfectly_nested_loops` correctly returns only the top + or outermost loop for a nest of loops where each qualifies as 'perfectly + nested'. + """ + schedule = get_schedule(fortran_reader, cs.quadruple_loop_with_1_assignment) + loops = get_perfectly_nested_loops(schedule) + + # there are four loops in the nest but we only want to return the top level + assert len(loops) == 1 + # top level loop uses 'l' as variable + assert loops[0].variable.name == "l" From 3e1269e6266945c16b3057557fc68593ed1e63c4 Mon Sep 17 00:00:00 2001 From: Oakley Brunt Date: Thu, 26 Jun 2025 14:05:02 +0100 Subject: [PATCH 2/8] #115 fix modification of list within for loop --- psytran/loop.py | 16 ++++++++-------- test/test_loop.py | 10 ++++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/psytran/loop.py b/psytran/loop.py index 9cd83e7..15ad1d3 100644 --- a/psytran/loop.py +++ b/psytran/loop.py @@ -218,7 +218,7 @@ def is_parallelisable(loop): return loop.independent_iterations() -def get_perfectly_nested_loops(schedule): +def get_perfectly_nested_outer_loops(schedule): """ Finds the topmost level of all perfectly nested loop structures within a specified schedule and retruns them as a list. @@ -230,19 +230,19 @@ def get_perfectly_nested_loops(schedule): structure. :rtype loops: list[:py:class:`Loop`] """ - perfectly_nested_loops = [] + perfectly_nested_outer_loops = [] # Test all loops in schedule to see if they are perfectly nested - for loop in schedule.walk(nodes.Loop): + for loop in schedule.walk(nodes.Loop): if is_perfectly_nested(loop): - perfectly_nested_loops.append(loop) + perfectly_nested_outer_loops.append(loop) # Check all loops that are perfectly nested to see if they are a descendent # of another perfectly nested loop and remove them if they are - for top_loop in perfectly_nested_loops: - for bottom_loop in reversed(perfectly_nested_loops): + for top_loop in perfectly_nested_outer_loops[:]: + for bottom_loop in reversed(perfectly_nested_outer_loops[:]): if bottom_loop in get_descendents(top_loop): - perfectly_nested_loops.remove(bottom_loop) + perfectly_nested_outer_loops.remove(bottom_loop) # return list of perfectly nested loops - return perfectly_nested_loops \ No newline at end of file + return perfectly_nested_outer_loops diff --git a/test/test_loop.py b/test/test_loop.py index 6ba0873..b92a435 100644 --- a/test/test_loop.py +++ b/test/test_loop.py @@ -20,7 +20,7 @@ is_parallelisable, is_perfectly_nested, is_simple_loop, - get_perfectly_nested_loops, + get_perfectly_nested_outer_loops, ) perfectly_nested_loop = { @@ -266,7 +266,7 @@ def test_get_perfectly_nested_sibling_loops(fortran_reader): nested looping structures even when they are siblings. """ schedule = get_schedule(fortran_reader, cs.perfect_nested_loop_siblings) - loops = get_perfectly_nested_loops(schedule) + loops = get_perfectly_nested_outer_loops(schedule) assert len(loops) == 2 assert loops[0].variable.name == "j" assert loops[1].variable.name == "l" @@ -278,8 +278,10 @@ def test_get_perfectly_nested_loop_top_level(fortran_reader): or outermost loop for a nest of loops where each qualifies as 'perfectly nested'. """ - schedule = get_schedule(fortran_reader, cs.quadruple_loop_with_1_assignment) - loops = get_perfectly_nested_loops(schedule) + schedule = get_schedule( + fortran_reader, cs.quadruple_loop_with_1_assignment + ) + loops = get_perfectly_nested_outer_loops(schedule) # there are four loops in the nest but we only want to return the top level assert len(loops) == 1 From d0167c26a587a17365535f8c2c9e092eca13a1de Mon Sep 17 00:00:00 2001 From: Oakley Brunt Date: Thu, 26 Jun 2025 14:11:45 +0100 Subject: [PATCH 3/8] #1156 Update comments and fix a typo --- psytran/loop.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/psytran/loop.py b/psytran/loop.py index 15ad1d3..6093a79 100644 --- a/psytran/loop.py +++ b/psytran/loop.py @@ -220,13 +220,13 @@ def is_parallelisable(loop): def get_perfectly_nested_outer_loops(schedule): """ - Finds the topmost level of all perfectly nested loop structures within a - specified schedule and retruns them as a list. + Finds the outermost of all perfectly nested loop structures within a + supplied schedule and returns them as a list. :arg schedule: the schedule to search. :type schedule: :py:class:`Schedule` - :returns loops: all loops that are the topmost of a perfectly nested + :returns loops: all loops that are the outermost of a perfectly nested structure. :rtype loops: list[:py:class:`Loop`] """ @@ -237,8 +237,8 @@ def get_perfectly_nested_outer_loops(schedule): if is_perfectly_nested(loop): perfectly_nested_outer_loops.append(loop) - # Check all loops that are perfectly nested to see if they are a descendent - # of another perfectly nested loop and remove them if they are + # For each loop in the list check if loops below it are a descendent and + # remove them if they are for top_loop in perfectly_nested_outer_loops[:]: for bottom_loop in reversed(perfectly_nested_outer_loops[:]): if bottom_loop in get_descendents(top_loop): From 539b9c12f88d8b4a7eab2154874f37c2181155bd Mon Sep 17 00:00:00 2001 From: Oakley Brunt Date: Thu, 26 Jun 2025 14:15:08 +0100 Subject: [PATCH 4/8] #115 Shorten returned obj name --- psytran/loop.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/psytran/loop.py b/psytran/loop.py index 6093a79..c88ee06 100644 --- a/psytran/loop.py +++ b/psytran/loop.py @@ -226,23 +226,23 @@ def get_perfectly_nested_outer_loops(schedule): :arg schedule: the schedule to search. :type schedule: :py:class:`Schedule` - :returns loops: all loops that are the outermost of a perfectly nested - structure. + :returns loops: all loops that are the outermost of + a perfectly nested structure. :rtype loops: list[:py:class:`Loop`] """ - perfectly_nested_outer_loops = [] + loops = [] # Test all loops in schedule to see if they are perfectly nested for loop in schedule.walk(nodes.Loop): if is_perfectly_nested(loop): - perfectly_nested_outer_loops.append(loop) + loops.append(loop) # For each loop in the list check if loops below it are a descendent and # remove them if they are - for top_loop in perfectly_nested_outer_loops[:]: - for bottom_loop in reversed(perfectly_nested_outer_loops[:]): + for top_loop in loops[:]: + for bottom_loop in reversed(loops[:]): if bottom_loop in get_descendents(top_loop): - perfectly_nested_outer_loops.remove(bottom_loop) + loops.remove(bottom_loop) # return list of perfectly nested loops - return perfectly_nested_outer_loops + return loops From fcf7130378a672bac0760125722501943d1ee0bf Mon Sep 17 00:00:00 2001 From: Oakley Brunt Date: Thu, 26 Jun 2025 15:37:51 +0100 Subject: [PATCH 5/8] #115 Add more context --- psytran/loop.py | 9 ++++++--- test/test_loop.py | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/psytran/loop.py b/psytran/loop.py index c88ee06..3e8270a 100644 --- a/psytran/loop.py +++ b/psytran/loop.py @@ -218,16 +218,19 @@ def is_parallelisable(loop): return loop.independent_iterations() -def get_perfectly_nested_outer_loops(schedule): +def get_perfectly_nested_loops(schedule): """ Finds the outermost of all perfectly nested loop structures within a supplied schedule and returns them as a list. + Does NOT nexcessarily return an outer loop, only the topmost loop of a + perfectly nested structure. + :arg schedule: the schedule to search. :type schedule: :py:class:`Schedule` - :returns loops: all loops that are the outermost of - a perfectly nested structure. + :returns loops: all loops that are the outermost of a perfectly nested + structure. :rtype loops: list[:py:class:`Loop`] """ loops = [] diff --git a/test/test_loop.py b/test/test_loop.py index b92a435..4816b58 100644 --- a/test/test_loop.py +++ b/test/test_loop.py @@ -20,7 +20,7 @@ is_parallelisable, is_perfectly_nested, is_simple_loop, - get_perfectly_nested_outer_loops, + get_perfectly_nested_loops, ) perfectly_nested_loop = { @@ -266,8 +266,8 @@ def test_get_perfectly_nested_sibling_loops(fortran_reader): nested looping structures even when they are siblings. """ schedule = get_schedule(fortran_reader, cs.perfect_nested_loop_siblings) - loops = get_perfectly_nested_outer_loops(schedule) - assert len(loops) == 2 + loops = get_perfectly_nested_loops(schedule) + assert len(loops) == 0 assert loops[0].variable.name == "j" assert loops[1].variable.name == "l" @@ -281,7 +281,7 @@ def test_get_perfectly_nested_loop_top_level(fortran_reader): schedule = get_schedule( fortran_reader, cs.quadruple_loop_with_1_assignment ) - loops = get_perfectly_nested_outer_loops(schedule) + loops = get_perfectly_nested_loops(schedule) # there are four loops in the nest but we only want to return the top level assert len(loops) == 1 From 4a044dacf2604e8f7513e29c5f7fe5801aa06629 Mon Sep 17 00:00:00 2001 From: Oakley Brunt Date: Thu, 26 Jun 2025 15:53:58 +0100 Subject: [PATCH 6/8] #115 Add more testing --- test/code_snippets.py | 24 ------------------------ test/test_loop.py | 33 ++++++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/test/code_snippets.py b/test/code_snippets.py index 3849961..ad7c0cf 100644 --- a/test/code_snippets.py +++ b/test/code_snippets.py @@ -437,30 +437,6 @@ END PROGRAM test """ -perfect_nested_loop_siblings = """ - PROGRAM test - REAL :: a(10,10) - INTEGER :: i - INTEGER :: j - INTEGER :: k - INTEGER :: l - INTEGER :: m - - DO i = 1, 10 - DO j = 1, 10 - DO k = 1, 10 - a(i,j,k) = 0 - END DO - END DO - DO l = 1, 10 - DO m = 1, 10 - a(i,l,m) = 0 - END DO - END DO - END DO - END PROGRAM test - """ - array_assignment_1d = """ PROGRAM test REAL :: a(10) diff --git a/test/test_loop.py b/test/test_loop.py index 4816b58..5021d45 100644 --- a/test/test_loop.py +++ b/test/test_loop.py @@ -265,11 +265,12 @@ def test_get_perfectly_nested_sibling_loops(fortran_reader): Test that :func:`get_perfectly_nested_loops` correctly identifies perfectly nested looping structures even when they are siblings. """ - schedule = get_schedule(fortran_reader, cs.perfect_nested_loop_siblings) + schedule = get_schedule(fortran_reader, cs.double_loop_with_2_loops) loops = get_perfectly_nested_loops(schedule) - assert len(loops) == 0 - assert loops[0].variable.name == "j" - assert loops[1].variable.name == "l" + assert len(loops) == 2 + assert loops[0].variable.name == "i" + assert loops[1].variable.name == "i" + assert loops[0] is not loops[1] def test_get_perfectly_nested_loop_top_level(fortran_reader): @@ -283,7 +284,25 @@ def test_get_perfectly_nested_loop_top_level(fortran_reader): ) loops = get_perfectly_nested_loops(schedule) - # there are four loops in the nest but we only want to return the top level + # There are four loops in the nest but we only want to return the top level + assert len(loops) == 1 + # In this case, this should return an outer loop + assert is_outer_loop(loops[0]) + + +def test_get_perfectly_nested_loop_with_conditional(fortran_reader): + """ + Test that :func:`get_perfectly_nested_loops` correctly returns only the top + or outermost loop for a nest of loops where each qualifies as 'perfectly + nested'. + """ + schedule = get_schedule( + fortran_reader, cs.conditional_imperfectly_nested_triple_loop1 + ) + loops = get_perfectly_nested_loops(schedule) + + # There are four loops in the nest but we only want to return the top level assert len(loops) == 1 - # top level loop uses 'l' as variable - assert loops[0].variable.name == "l" + # In this case, this should return an outer loop + assert not is_outer_loop(loops[0]) + assert loops[0].variable.name == "j" From 8ab3249983c71b26c7cab1fcf31f0cc4b9550260 Mon Sep 17 00:00:00 2001 From: Oakley Brunt <130391369+oakleybrunt@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:19:11 +0100 Subject: [PATCH 7/8] Remove unnecessary comment Co-authored-by: Joe Wallwork <22053413+jwallwork23@users.noreply.github.com> --- psytran/loop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/psytran/loop.py b/psytran/loop.py index 3e8270a..0b52dcd 100644 --- a/psytran/loop.py +++ b/psytran/loop.py @@ -247,5 +247,4 @@ def get_perfectly_nested_loops(schedule): if bottom_loop in get_descendents(top_loop): loops.remove(bottom_loop) - # return list of perfectly nested loops return loops From 741f51039865d746e0a52679e4a39b145f457f72 Mon Sep 17 00:00:00 2001 From: Oakley Brunt <130391369+oakleybrunt@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:25:42 +0100 Subject: [PATCH 8/8] More efficient looping Co-authored-by: Joe Wallwork <22053413+jwallwork23@users.noreply.github.com> --- psytran/loop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psytran/loop.py b/psytran/loop.py index 0b52dcd..f96dfb2 100644 --- a/psytran/loop.py +++ b/psytran/loop.py @@ -243,8 +243,8 @@ def get_perfectly_nested_loops(schedule): # For each loop in the list check if loops below it are a descendent and # remove them if they are for top_loop in loops[:]: - for bottom_loop in reversed(loops[:]): - if bottom_loop in get_descendents(top_loop): + for bottom_loop in get_descendents(top_loop): + if bottom_loop in loops: loops.remove(bottom_loop) return loops