Example #1
0
    def test_get_node_info_multiple_call_links(self):
        """Test the `get_node_info` utility.

        Regression test for #2868:
            Verify that all `CALL` links are included in the formatted string even if link labels are identical.
        """
        from aiida.cmdline.utils.common import get_node_info

        workflow = orm.WorkflowNode().store()
        node_one = orm.CalculationNode()
        node_two = orm.CalculationNode()

        node_one.add_incoming(workflow,
                              link_type=LinkType.CALL_CALC,
                              link_label='CALL_IDENTICAL')
        node_two.add_incoming(workflow,
                              link_type=LinkType.CALL_CALC,
                              link_label='CALL_IDENTICAL')
        node_one.store()
        node_two.store()

        node_info = get_node_info(workflow)
        self.assertTrue('CALL_IDENTICAL' in node_info)
        self.assertTrue(str(node_one.pk) in node_info)
        self.assertTrue(str(node_two.pk) in node_info)
Example #2
0
    def test_double_return_links_for_workflows(self, temp_dir):
        """
        This test checks that double return links to a node can be exported
        and imported without problems,
        """
        work1 = orm.WorkflowNode()
        work2 = orm.WorkflowNode().store()
        data_in = orm.Int(1).store()
        data_out = orm.Int(2).store()

        work1.add_incoming(data_in, LinkType.INPUT_WORK, 'input_i1')
        work1.add_incoming(work2, LinkType.CALL_WORK, 'call')
        work1.store()
        data_out.add_incoming(work1, LinkType.RETURN, 'return1')
        data_out.add_incoming(work2, LinkType.RETURN, 'return2')
        links_count = 4

        work1.seal()
        work2.seal()

        uuids_wanted = set(_.uuid for _ in (work1, data_out, data_in, work2))
        links_wanted = get_all_node_links()

        export_file = os.path.join(temp_dir, 'export.tar.gz')
        export([data_out, work1, work2, data_in],
               outfile=export_file,
               silent=True)

        self.reset_database()

        import_data(export_file, silent=True)

        uuids_in_db = [
            str(uuid) for [uuid] in orm.QueryBuilder().append(
                orm.Node, project='uuid').all()
        ]
        self.assertListEqual(sorted(uuids_wanted), sorted(uuids_in_db))

        links_in_db = get_all_node_links()
        self.assertListEqual(sorted(links_wanted), sorted(links_in_db))

        # Assert number of links, checking both RETURN links are included
        self.assertEqual(len(links_wanted), links_count)  # Before export
        self.assertEqual(len(links_in_db), links_count)  # After import
def test_previous_workchain(run_cli_command):
    """Test the ``options.PREVIOUS_WORKCHAIN`` option."""
    node = orm.WorkflowNode().store()

    @click.command()
    @options.PREVIOUS_WORKCHAIN()
    def command(previous_workchain):
        assert previous_workchain.pk == node.pk

    run_cli_command(command, ['--previous-workchain', str(node.pk)])
Example #4
0
def test_reference_workchain(run_cli_command):
    """Test the ``options.REFERENCE_WORKCHAIN`` option."""
    node = orm.WorkflowNode().store()

    @click.command()
    @options.REFERENCE_WORKCHAIN()
    def command(reference_workchain):
        assert reference_workchain.pk == node.pk

    run_cli_command(command, ['--reference-workchain', str(node.pk)])
Example #5
0
    def test_exposed_outputs(self):
        """Test the ``Process.exposed_outputs`` method."""
        from aiida.common import AttributeDict
        from aiida.common.links import LinkType
        from aiida.engine.utils import instantiate_process
        from aiida.manage.manager import get_manager

        runner = get_manager().get_runner()

        class ChildProcess(Process):
            """Dummy process with normal output and output namespace."""

            _node_class = orm.WorkflowNode

            @classmethod
            def define(cls, spec):
                super(ChildProcess, cls).define(spec)
                spec.input('input', valid_type=orm.Int)
                spec.output('output', valid_type=orm.Int)
                spec.output('name.space', valid_type=orm.Int)

        class ParentProcess(Process):
            """Dummy process that exposes the outputs of ``ChildProcess``."""

            _node_class = orm.WorkflowNode

            @classmethod
            def define(cls, spec):
                super(ParentProcess, cls).define(spec)
                spec.input('input', valid_type=orm.Int)
                spec.expose_outputs(ChildProcess)

        node_child = orm.WorkflowNode().store()
        node_output = orm.Int(1).store()
        node_output.add_incoming(node_child, link_label='output', link_type=LinkType.RETURN)
        node_name_space = orm.Int(1).store()
        node_name_space.add_incoming(node_child, link_label='name__space', link_type=LinkType.RETURN)

        process = instantiate_process(runner, ParentProcess, input=orm.Int(1))
        exposed_outputs = process.exposed_outputs(node_child, ChildProcess)

        expected = AttributeDict({
            'name': {
                'space': node_name_space,
            },
            'output': node_output,
        })
        self.assertEqual(exposed_outputs, expected)
Example #6
0
    def test_cycle(self):
        """
        Testing the case of a cycle (workflow node with a data node that is
        both an input and an output):
        - Update rules with no max iterations should not get stuck.
        - Replace rules should return alternating results.
        """
        data_node = orm.Data().store()
        work_node = orm.WorkflowNode()
        work_node.add_incoming(data_node,
                               link_type=LinkType.INPUT_WORK,
                               link_label='input_link')
        work_node.store()
        data_node.add_incoming(work_node,
                               link_type=LinkType.RETURN,
                               link_label='return_link')

        basket = Basket(nodes=[data_node.id])
        queryb = orm.QueryBuilder()
        queryb.append(orm.Node).append(orm.Node)

        uprule = UpdateRule(queryb, max_iterations=np.inf)
        obtained = uprule.run(basket.copy())['nodes'].keyset
        expected = set([data_node.id, work_node.id])
        self.assertEqual(obtained, expected)

        rerule1 = ReplaceRule(queryb, max_iterations=1)
        result1 = rerule1.run(basket.copy())['nodes'].keyset
        self.assertEqual(result1, set([work_node.id]))

        rerule2 = ReplaceRule(queryb, max_iterations=2)
        result2 = rerule2.run(basket.copy())['nodes'].keyset
        self.assertEqual(result2, set([data_node.id]))

        rerule3 = ReplaceRule(queryb, max_iterations=3)
        result3 = rerule3.run(basket.copy())['nodes'].keyset
        self.assertEqual(result3, set([work_node.id]))

        rerule4 = ReplaceRule(queryb, max_iterations=4)
        result4 = rerule4.run(basket.copy())['nodes'].keyset
        self.assertEqual(result4, set([data_node.id]))
Example #7
0
    def _create_basic_graph():
        """
        Creates a basic graph which has one parent workflow (work_2) that calls
        a child workflow (work_1) which calls a calculation function (calc_0).
        There is one input (data_i) and one output (data_o). It has at least one
        link of each class:

        * CALL_WORK from work_2 to work_1.
        * CALL_CALC from work_1 to calc_0.
        * INPUT_CALC from data_i to calc_0 and CREATE from calc_0 to data_o.
        * INPUT_WORK from data_i to work_1 and RETURN from work_1 to data_o.
        * INPUT_WORK from data_i to work_2 and RETURN from work_2 to data_o.

        This graph looks like this::

                     input_work      +--------+       return
               +-------------------> | work_2 | --------------------+
               |                     +--------+                     |
               |                         |                          |
               |                         | call_work                |
               |                         |                          |
               |                         v                          |
               |       input_work    +--------+      return         |
               |  +----------------> | work_1 | -----------------+  |
               |  |                  +--------+                  |  |
               |  |                      |                       |  |
               |  |                      | call_calc             |  |
               |  |                      |                       |  |
               |  |                      v                       v  v
            +--------+  input_calc   +--------+    create     +--------+
            | data_i | ------------> | calc_0 | ------------> | data_o |
            +--------+               +--------+               +--------+
        """
        data_i = orm.Data().store()

        work_2 = orm.WorkflowNode()
        work_2.add_incoming(data_i, link_type=LinkType.INPUT_WORK, link_label='inpwork2')
        work_2.store()

        work_1 = orm.WorkflowNode()
        work_1.add_incoming(data_i, link_type=LinkType.INPUT_WORK, link_label='inpwork1')
        work_1.add_incoming(work_2, link_type=LinkType.CALL_WORK, link_label='callwork')
        work_1.store()

        calc_0 = orm.CalculationNode()
        calc_0.add_incoming(data_i, link_type=LinkType.INPUT_CALC, link_label='inpcalc0')
        calc_0.add_incoming(work_1, link_type=LinkType.CALL_CALC, link_label='callcalc')
        calc_0.store()

        data_o = orm.Data()
        data_o.add_incoming(calc_0, link_type=LinkType.CREATE, link_label='create0')
        data_o.store()
        data_o.add_incoming(work_2, link_type=LinkType.RETURN, link_label='return2')
        data_o.add_incoming(work_1, link_type=LinkType.RETURN, link_label='return1')

        output_dict = {
            'data_i': data_i,
            'data_o': data_o,
            'calc_0': calc_0,
            'work_1': work_1,
            'work_2': work_2,
        }
        return output_dict
Example #8
0
    def test_multiple_post_return_links(self, temp_dir):  # pylint: disable=too-many-locals
        """Check extra RETURN links can be added to existing Nodes, when label is not unique"""
        data = orm.Int(1).store()
        calc = orm.CalculationNode().store()
        work = orm.WorkflowNode().store()
        link_label = 'output_data'

        data.add_incoming(calc, LinkType.CREATE, link_label)
        data.add_incoming(work, LinkType.RETURN, link_label)

        calc.seal()
        work.seal()

        data_uuid = data.uuid
        calc_uuid = calc.uuid
        work_uuid = work.uuid
        before_links = get_all_node_links()

        data_provenance = os.path.join(temp_dir, 'data.aiida')
        all_provenance = os.path.join(temp_dir, 'all.aiida')

        export([data], filename=data_provenance, return_backward=False)
        export([data], filename=all_provenance, return_backward=True)

        self.clean_db()

        # import data provenance
        import_data(data_provenance)

        no_of_work = orm.QueryBuilder().append(orm.WorkflowNode).count()
        self.assertEqual(
            no_of_work,
            0,
            msg=
            f'{no_of_work} WorkflowNode(s) was/were found, however, none should be present'
        )

        nodes = orm.QueryBuilder().append(orm.Node, project='uuid')
        self.assertEqual(
            nodes.count(),
            2,
            msg=
            f'{no_of_work} Node(s) was/were found, however, exactly two should be present'
        )
        for node in nodes.iterall():
            self.assertIn(node[0], [data_uuid, calc_uuid])

        links = get_all_node_links()
        self.assertEqual(
            len(links),
            1,
            msg='Only a single Link (from Calc. to Data) is expected, '
            'instead {} were found (in, out, label, type): {}'.format(
                len(links), links))
        for from_uuid, to_uuid, found_label, found_type in links:
            self.assertEqual(from_uuid, calc_uuid)
            self.assertEqual(to_uuid, data_uuid)
            self.assertEqual(found_label, link_label)
            self.assertEqual(found_type, LinkType.CREATE.value)

        # import data+logic provenance
        import_data(all_provenance)

        no_of_work = orm.QueryBuilder().append(orm.WorkflowNode).count()
        self.assertEqual(
            no_of_work,
            1,
            msg=
            f'{no_of_work} WorkflowNode(s) was/were found, however, exactly one should be present'
        )

        nodes = orm.QueryBuilder().append(orm.Node, project='uuid')
        self.assertEqual(
            nodes.count(),
            3,
            msg=
            f'{no_of_work} Node(s) was/were found, however, exactly three should be present'
        )
        for node in nodes.iterall():
            self.assertIn(node[0], [data_uuid, calc_uuid, work_uuid])

        links = get_all_node_links()
        self.assertEqual(
            len(links),
            2,
            msg=
            f'Exactly two Links are expected, instead {len(links)} were found (in, out, label, type): {links}'
        )
        self.assertListEqual(sorted(links), sorted(before_links))
Example #9
0
    def test_traversal_cycle(self):
        """
        This will test that cycles don't go into infinite loops by testing a
        graph with two data nodes data_take and data_drop and a work_select
        that takes both as input but returns only data_take
        """
        from aiida import orm

        data_take = orm.Data().store()
        data_drop = orm.Data().store()
        work_select = orm.WorkflowNode()

        work_select.add_incoming(data_take,
                                 link_type=LinkType.INPUT_WORK,
                                 link_label='input_take')
        work_select.add_incoming(data_drop,
                                 link_type=LinkType.INPUT_WORK,
                                 link_label='input_drop')
        work_select.store()

        data_take.add_incoming(work_select,
                               link_type=LinkType.RETURN,
                               link_label='return_link')

        data_take = data_take.pk
        data_drop = data_drop.pk
        work_select = work_select.pk

        every_node = [data_take, data_drop, work_select]

        for single_node in every_node:
            expected_nodes = set([single_node])
            obtained_nodes = traverse_graph([single_node])['nodes']
            self.assertEqual(obtained_nodes, expected_nodes)

        links_forward = [LinkType.INPUT_WORK, LinkType.RETURN]
        links_backward = []

        # Forward: data_drop to (input) work_select to (return) data_take
        obtained_nodes = traverse_graph([data_drop],
                                        links_forward=links_forward,
                                        links_backward=links_backward)['nodes']
        expected_nodes = set(every_node)
        self.assertEqual(obtained_nodes, expected_nodes)

        # Forward: data_take to (input) work_select (data_drop is not returned)
        obtained_nodes = traverse_graph([data_take],
                                        links_forward=links_forward,
                                        links_backward=links_backward)['nodes']
        expected_nodes = set([work_select, data_take])
        self.assertEqual(obtained_nodes, expected_nodes)

        # Forward: work_select to (return) data_take (data_drop is not returned)
        obtained_nodes = traverse_graph([work_select],
                                        links_forward=links_forward,
                                        links_backward=links_backward)['nodes']
        self.assertEqual(obtained_nodes, expected_nodes)

        links_forward = []
        links_backward = [LinkType.INPUT_WORK, LinkType.RETURN]

        # Backward: data_drop is not returned so it has no backward link
        expected_nodes = set([data_drop])
        obtained_nodes = traverse_graph([data_drop],
                                        links_forward=links_forward,
                                        links_backward=links_backward)['nodes']
        self.assertEqual(obtained_nodes, expected_nodes)

        # Backward: data_take to (return) work_select to (input) data_drop
        expected_nodes = set(every_node)
        obtained_nodes = traverse_graph([data_take],
                                        links_forward=links_forward,
                                        links_backward=links_backward)['nodes']
        self.assertEqual(obtained_nodes, expected_nodes)

        # Backward: work_select to (input) data_take and data_drop
        obtained_nodes = traverse_graph([work_select],
                                        links_forward=links_forward,
                                        links_backward=links_backward)['nodes']
        self.assertEqual(obtained_nodes, expected_nodes)
Example #10
0
def create_minimal_graph():
    """
    Creates a minimal graph which has one parent workflow (W2) that calls
    a child workflow (W1) which calls a calculation function (C0). There
    is one input (DI) and one output (DO). It has at least one link of
    each class:

    * CALL_WORK from W2 to W1.
    * CALL_CALC from W1 to C0.
    * INPUT_CALC from DI to C0 and CREATE from C0 to DO.
    * INPUT_WORK from DI to W1 and RETURN from W1 to DO.
    * INPUT_WORK from DI to W2 and RETURN from W2 to DO.

    This graph looks like this::

              input_work     +----+     return
          +----------------> | W2 | ---------------+
          |                  +----+                |
          |                    |                   |
          |                    | call_work         |
          |                    |                   |
          |                    v                   |
          |     input_work   +----+    return      |
          |  +-------------> | W1 | ------------+  |
          |  |               +----+             |  |
          |  |                 |                |  |
          |  |                 | call_calc      |  |
          |  |                 |                |  |
          |  |                 v                v  v
        +------+ input_calc  +----+  create   +------+
        |  DI  | ----------> | C0 | --------> |  DO  |
        +------+             +----+           +------+
    """
    from aiida import orm

    data_i = orm.Data().store()
    data_o = orm.Data().store()

    calc_0 = orm.CalculationNode()
    work_1 = orm.WorkflowNode()
    work_2 = orm.WorkflowNode()

    calc_0.add_incoming(data_i,
                        link_type=LinkType.INPUT_CALC,
                        link_label='inpcalc')
    work_1.add_incoming(data_i,
                        link_type=LinkType.INPUT_WORK,
                        link_label='inpwork')
    work_2.add_incoming(data_i,
                        link_type=LinkType.INPUT_WORK,
                        link_label='inpwork')

    calc_0.add_incoming(work_1,
                        link_type=LinkType.CALL_CALC,
                        link_label='callcalc')
    work_1.add_incoming(work_2,
                        link_type=LinkType.CALL_WORK,
                        link_label='callwork')

    work_2.store()
    work_1.store()
    calc_0.store()

    data_o.add_incoming(calc_0,
                        link_type=LinkType.CREATE,
                        link_label='create0')
    data_o.add_incoming(work_1,
                        link_type=LinkType.RETURN,
                        link_label='return1')
    data_o.add_incoming(work_2,
                        link_type=LinkType.RETURN,
                        link_label='return2')

    output_dict = {
        'data_i': data_i,
        'data_o': data_o,
        'calc_0': calc_0,
        'work_1': work_1,
        'work_2': work_2,
    }

    return output_dict