Repository: nipy/nipype

Issue:
Issue with node name that starts with another node's name
I think the [line ~801 in util.py](https://github.com/nipy/nipype/edit/master/nipype/pipeline/engine/utils.py#L801) should be something like this:

            for node in graph_in.nodes():
                for src_id, edge_data in list(old_edge_dict.items()):
                    if node.itername.startswith(src_id + '.'):  # <-- add '.' to src_id
                        expansions[src_id].append(node)

For example, if the node "input" feeds into "input_files", the "input_files" can be included if you just test for node.itername.startswith(src_id). This change would prevent "input_files" from being included. 

Edit: removed last part of my comment.


Discussion:
@williford - could you give us a brief example of workflow where this fails? the intent of having the period (`.`) was to prevent partial matches and workflows cannot have two nodes with the same name.
@satra The ` + '.'` is what I'm suggesting to add, it currently isn't in the code. I'm saying that it should be added to prevent partial matches. Sorry for the confusion, I removed the second block of code in my first post.

The current version of code fails when "input" feeds into "input_files" and "input_files" is a JoinNode. Adding the period should fix that. I wasn't sure about posting a PR because I wasn't sure if adding a period would break anything else.
thanks @williford - would you be able to give us a dummy example where this breaks? even editing this workflow as necessary would be sufficient: 

https://miykael.github.io/nipype_tutorial/notebooks/introduction_quickstart.html

i'm recollecting that @mgxd and i went over a PR to address this not too long back, so i just want to make sure we have a test addressing this scenario first before making any changes.
I was able to reproduce this:

```Python
from nipype import Workflow, Node, JoinNode
from nipype.interfaces.utility import Merge, Function


def sq(x):
    return x ** 2


wf = Workflow('wf')

square = Node(Function(function=sq), name='square')
square.iterables = ('x', [1, 2])

square_join = JoinNode(Merge(1, ravel_inputs=True),
                       name='square_join',
                       joinsource=square,
                       joinfield=['in1'])

wf.connect(square, 'out', square_join, "in1")

wf.run()
```

Resolving patch:
diff --git a/nipype/pipeline/engine/utils.py b/nipype/pipeline/engine/utils.py
--- a/nipype/pipeline/engine/utils.py
+++ b/nipype/pipeline/engine/utils.py
@@ -1050,7 +1050,17 @@ def make_field_func(*pair):
             expansions = defaultdict(list)
             for node in graph_in.nodes():
                 for src_id in list(old_edge_dict.keys()):
-                    if node.itername.startswith(src_id):
+                    # Drop the original JoinNodes; only concerned with
+                    # generated Nodes
+                    if hasattr(node, 'joinfield'):
+                        continue
+                    # Patterns:
+                    #   - src_id : Non-iterable node
+                    #   - src_id.[a-z]\d+ : IdentityInterface w/ iterables
+                    #   - src_id.[a-z]I.[a-z]\d+ : Non-IdentityInterface w/ iterables
+                    #   - src_idJ\d+ : JoinNode(IdentityInterface)
+                    if re.match(src_id + r'((\.[a-z](I\.[a-z])?|J)\d+)?$',
+                                node.itername):
                         expansions[src_id].append(node)
             for in_id, in_nodes in list(expansions.items()):
                 logger.debug("The join node %s input %s was expanded"


Test patch:
diff --git a/nipype/pipeline/engine/tests/test_join.py b/nipype/pipeline/engine/tests/test_join.py
--- a/nipype/pipeline/engine/tests/test_join.py
+++ b/nipype/pipeline/engine/tests/test_join.py
@@ -7,11 +7,9 @@
                         absolute_import)
 from builtins import open
 
-import os
-
 from ... import engine as pe
 from ....interfaces import base as nib
-from ....interfaces.utility import IdentityInterface
+from ....interfaces.utility import IdentityInterface, Function, Merge
 from ....interfaces.base import traits, File
 
 
@@ -612,3 +610,20 @@ def nested_wf(i, name='smallwf'):
     # there should be six nodes in total
     assert len(result.nodes()) == 6, \
         "The number of expanded nodes is incorrect."
+
+
+def test_name_prefix_join(tmpdir):
+    tmpdir.chdir()
+
+    def sq(x):
+        return x ** 2
+
+    wf = pe.Workflow('wf', base_dir=tmpdir.strpath)
+    square = pe.Node(Function(function=sq), name='square')
+    square.iterables = [('x', [1, 2])]
+    square_join = pe.JoinNode(Merge(1, ravel_inputs=True),
+                              name='square_join',
+                              joinsource='square',
+                              joinfield=['in1'])
+    wf.connect(square, 'out', square_join, "in1")
+    wf.run()