Skip to content
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

Replace .inp and .out for Node with .inputs and .outputs for CalculationNode and WorkflowNode #2569

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ci/polish/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def launch(expression, code, use_calculations, use_calcfunctions, sleep, timeout
sys.exit(1)

try:
result = workchain.out.result
result = workchain.outputs.result
except AttributeError:
click.secho('Failed: ', fg='red', bold=True, nl=False)
click.secho('the workchain<{}> did not return a result output node'.format(workchain.pk), bold=True)
Expand Down
4 changes: 2 additions & 2 deletions .ci/polish/lib/template/workchain.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ ${outline}
return self.ERROR_NO_JOB_CALCULATION

try:
self.ctx.result = calculation.out.sum % self.inputs.modulo
self.ctx.result = calculation.outputs.sum % self.inputs.modulo
except AttributeError as exception:
self.report('no output node found')
return self.ERROR_NO_OUTPUT_NODE
Expand Down Expand Up @@ -118,7 +118,7 @@ ${outline}
def post_raise_power(self):
sub_result = prod(self.ctx.iterators_sign)
for workchain in self.ctx.workchains:
sub_result *= workchain.out.result.value
sub_result *= workchain.outputs.result.value
sub_result %= self.inputs.modulo.value

self.ctx.result += sub_result
Expand Down
4 changes: 2 additions & 2 deletions .ci/test_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def validate_calculations(expected_results):
valid = False

try:
actual_dict = calc.out.output_parameters.get_dict()
actual_dict = calc.outputs.output_parameters.get_dict()
except exceptions.NotExistent:
print('Could not retrieve `output_parameters` node for Calculation<{}>'.format(pk))
print_report(pk)
Expand All @@ -112,7 +112,7 @@ def validate_workchains(expected_results):
this_valid = True
try:
calc = load_node(pk)
actual_value = calc.out.output
actual_value = calc.outputs.output
except (exceptions.NotExistent, AttributeError) as exception:
print("* UNABLE TO RETRIEVE VALUE for workchain pk={}: I expected {}, I got {}: {}"
.format(pk, expected_value, type(exception), exception))
Expand Down
2 changes: 1 addition & 1 deletion .ci/workchains.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def finalize(self):
if self.should_submit():
self.report('Getting sub-workchain output.')
sub_workchain = self.ctx.workchain[0]
self.out('output', sub_workchain.out.output + 1)
self.out('output', sub_workchain.outputs.output + 1)
else:
self.report('Bottom-level workchain reached.')
self.out('output', Int(0))
Expand Down
6 changes: 3 additions & 3 deletions aiida/backends/tests/engine/test_calcfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ def test_calcfunction_default_linkname(self):
"""Verify that a calcfunction that returns a single Data node gets a default link label."""
_, node = self.test_calcfunction.run_get_node(self.default_int)

self.assertEqual(node.out.result, self.default_int.value + 1)
self.assertEqual(getattr(node.out, Process.SINGLE_OUTPUT_LINKNAME), self.default_int.value + 1)
self.assertEqual(node.out[Process.SINGLE_OUTPUT_LINKNAME], self.default_int.value + 1)
self.assertEqual(node.outputs.result, self.default_int.value + 1)
self.assertEqual(getattr(node.outputs, Process.SINGLE_OUTPUT_LINKNAME), self.default_int.value + 1)
self.assertEqual(node.outputs[Process.SINGLE_OUTPUT_LINKNAME], self.default_int.value + 1)

def test_calcfunction_caching(self):
"""Verify that a calcfunction can be cached."""
Expand Down
16 changes: 8 additions & 8 deletions aiida/backends/tests/engine/test_work_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,15 +409,15 @@ def s1(self):
return ToContext(r1=self.submit(ReturnA), r2=self.submit(ReturnB))

def s2(self):
test_case.assertEquals(self.ctx.r1.out.res, A)
test_case.assertEquals(self.ctx.r2.out.res, B)
test_case.assertEquals(self.ctx.r1.outputs.res, A)
test_case.assertEquals(self.ctx.r2.outputs.res, B)

# Try overwriting r1
return ToContext(r1=self.submit(ReturnB))

def s3(self):
test_case.assertEquals(self.ctx.r1.out.res, B)
test_case.assertEquals(self.ctx.r2.out.res, B)
test_case.assertEquals(self.ctx.r1.outputs.res, B)
test_case.assertEquals(self.ctx.r2.outputs.res, B)

run_and_check_success(Wf)

Expand Down Expand Up @@ -519,7 +519,7 @@ def do_run(self):

def check(self):
pass
assert self.ctx.subwc.out.value == Int(5)
assert self.ctx.subwc.outputs.value == Int(5)

class SubWorkChain(WorkChain):

Expand Down Expand Up @@ -547,7 +547,7 @@ def do_run(self):
return ToContext(subwc=self.submit(SubWorkChain))

def check(self):
assert self.ctx.subwc.out.value == Int(5)
assert self.ctx.subwc.outputs.value == Int(5)

class SubWorkChain(WorkChain):

Expand Down Expand Up @@ -649,8 +649,8 @@ def begin(self):
return ToContext(result_b=self.submit(SimpleWc))

def result(self):
test_case.assertEquals(self.ctx.result_a.out._return, val)
test_case.assertEquals(self.ctx.result_b.out._return, val)
test_case.assertEquals(self.ctx.result_a.outputs._return, val)
test_case.assertEquals(self.ctx.result_b.outputs._return, val)

run_and_check_success(Workchain)

Expand Down
6 changes: 3 additions & 3 deletions aiida/backends/tests/engine/test_workfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ def test_workfunction_default_linkname(self):
"""Verify that a workfunction that returns a single Data node gets a default link label."""
_, node = self.test_workfunction.run_get_node(self.default_int)

self.assertEqual(node.out.result, self.default_int)
self.assertEqual(getattr(node.out, Process.SINGLE_OUTPUT_LINKNAME), self.default_int)
self.assertEqual(node.out[Process.SINGLE_OUTPUT_LINKNAME], self.default_int)
self.assertEqual(node.outputs.result, self.default_int)
self.assertEqual(getattr(node.outputs, Process.SINGLE_OUTPUT_LINKNAME), self.default_int)
self.assertEqual(node.outputs[Process.SINGLE_OUTPUT_LINKNAME], self.default_int)

def test_workfunction_caching(self):
"""Verify that a workfunction cannot be cached."""
Expand Down
103 changes: 103 additions & 0 deletions aiida/backends/tests/orm/node/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,106 @@ def test_node_outdegree_unique_triple(self):
uuids_outgoing = set(node.uuid for node in creator.get_outgoing().all_nodes())
uuids_expected = set([data_one.uuid, data_two.uuid])
self.assertEqual(uuids_outgoing, uuids_expected)

def test_get_node_by_label(self):
"""Test the get_node_by_label() method of the `LinkManager`

In particular, check both the it returns the correct values, but also that it raises the expected
exceptions where appropriate (missing link with a given label, or more than one link)
"""
data = Data().store()
calc_one_a = CalculationNode().store()
calc_one_b = CalculationNode().store()
calc_two = CalculationNode().store()

# Two calcs using the data with the same label
calc_one_a.add_incoming(data, link_type=LinkType.INPUT_CALC, link_label='input')
calc_one_b.add_incoming(data, link_type=LinkType.INPUT_CALC, link_label='input')
# A different label
calc_two.add_incoming(data, link_type=LinkType.INPUT_CALC, link_label='the_input')

# Retrieve a link when the label is unique
output_the_input = data.get_outgoing(link_type=LinkType.INPUT_CALC).get_node_by_label('the_input')
self.assertEqual(output_the_input.pk, calc_two.pk)

with self.assertRaises(exceptions.MultipleObjectsError):
data.get_outgoing(link_type=LinkType.INPUT_CALC).get_node_by_label('input')

with self.assertRaises(exceptions.NotExistent):
data.get_outgoing(link_type=LinkType.INPUT_CALC).get_node_by_label('some_weird_label')

def test_tab_completable_properties(self):
"""Test properties to go from one node to a neighboring one"""
input1 = Data().store()
input2 = Data().store()

top_workflow = WorkflowNode().store()
workflow = WorkflowNode().store()
calc1 = CalculationNode().store()
calc2 = CalculationNode().store()

output1 = Data().store()
output2 = Data().store()

# top_workflow has two inputs, proxies them to workflow, that in turn
# calls two calcs (passing 1 data to each),
# and return the two data nodes returned one by each called calculation
top_workflow.add_incoming(input1, link_type=LinkType.INPUT_WORK, link_label='a')
top_workflow.add_incoming(input2, link_type=LinkType.INPUT_WORK, link_label='b')

workflow.add_incoming(input1, link_type=LinkType.INPUT_WORK, link_label='a')
workflow.add_incoming(input2, link_type=LinkType.INPUT_WORK, link_label='b')
workflow.add_incoming(top_workflow, link_type=LinkType.CALL_WORK, link_label='CALL')

calc1.add_incoming(input1, link_type=LinkType.INPUT_CALC, link_label='input_value')
calc1.add_incoming(workflow, link_type=LinkType.CALL_CALC, link_label='CALL')
output1.add_incoming(calc1, link_type=LinkType.CREATE, link_label='result')

calc2.add_incoming(input2, link_type=LinkType.INPUT_CALC, link_label='input_value')
calc2.add_incoming(workflow, link_type=LinkType.CALL_CALC, link_label='CALL')
output2.add_incoming(calc2, link_type=LinkType.CREATE, link_label='result')

output1.add_incoming(workflow, link_type=LinkType.RETURN, link_label='result_a')
output2.add_incoming(workflow, link_type=LinkType.RETURN, link_label='result_b')
output1.add_incoming(top_workflow, link_type=LinkType.RETURN, link_label='result_a')
output2.add_incoming(top_workflow, link_type=LinkType.RETURN, link_label='result_b')

## Now we test the methods
# creator
self.assertEqual(output1.creator.pk, calc1.pk)
self.assertEqual(output2.creator.pk, calc2.pk)

# caller (for calculations)
self.assertEqual(calc1.caller.pk, workflow.pk)
self.assertEqual(calc2.caller.pk, workflow.pk)

# caller (for workflows)
self.assertEqual(workflow.caller.pk, top_workflow.pk)

# .inputs for calculations
self.assertEqual(calc1.inputs.input_value.pk, input1.pk)
self.assertEqual(calc2.inputs.input_value.pk, input2.pk)
with self.assertRaises(exceptions.NotExistent):
_ = calc1.inputs.some_label

# .inputs for workflows
self.assertEqual(top_workflow.inputs.a.pk, input1.pk)
self.assertEqual(top_workflow.inputs.b.pk, input2.pk)
self.assertEqual(workflow.inputs.a.pk, input1.pk)
self.assertEqual(workflow.inputs.b.pk, input2.pk)
with self.assertRaises(exceptions.NotExistent):
_ = workflow.inputs.some_label

# .outputs for calculations
self.assertEqual(calc1.outputs.result.pk, output1.pk)
self.assertEqual(calc2.outputs.result.pk, output2.pk)
with self.assertRaises(exceptions.NotExistent):
_ = calc1.outputs.some_label

# .outputs for workflows
self.assertEqual(top_workflow.outputs.result_a.pk, output1.pk)
self.assertEqual(top_workflow.outputs.result_b.pk, output2.pk)
self.assertEqual(workflow.outputs.result_a.pk, output1.pk)
self.assertEqual(workflow.outputs.result_b.pk, output2.pk)
with self.assertRaises(exceptions.NotExistent):
_ = workflow.outputs.some_label # noqa
4 changes: 2 additions & 2 deletions aiida/backends/tests/test_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def output_test(pk, testname, skip_uuids_from_inputs=[]):
folder = Folder(outfolder)
to_export = [c.dbnode] + inputs
try:
to_export.append(c.out.retrieved.dbnode)
to_export.append(c.outputs.retrieved.dbnode)
except AttributeError:
raise ValueError("No output retrieved node; without it, we cannot test the parser!")
export_tree(to_export, folder=folder, also_parents=False, also_calc_outputs=False)
Expand Down Expand Up @@ -188,7 +188,7 @@ def read_test(self, outfolder):
calc = c
break

retrieved = calc.out.retrieved
retrieved = calc.outputs.retrieved

try:
with io.open(os.path.join(outfolder, '_aiida_checks.json', encoding='utf8')) as fhandle:
Expand Down
4 changes: 2 additions & 2 deletions aiida/cmdline/commands/cmd_calcjob.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def calcjob_outputcat(calcjob, path):
entry_point.name))

try:
retrieved = calcjob.out.retrieved
retrieved = calcjob.outputs.retrieved
except AttributeError:
echo.echo_critical("No 'retrieved' node found. Have the calcjob files already been retrieved?")

Expand Down Expand Up @@ -178,7 +178,7 @@ def calcjob_outputls(calcjob, path, color):
from aiida.cmdline.utils.repository import list_repository_contents

try:
retrieved = calcjob.out.retrieved
retrieved = calcjob.outputs.retrieved
except AttributeError:
echo.echo_critical("No 'retrieved' node found. Have the calcjob files already been retrieved?")

Expand Down
1 change: 1 addition & 0 deletions aiida/cmdline/utils/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from __future__ import absolute_import

DEFAULT_MODULES_LIST = [
('aiida.common.links', 'LinkType', 'LinkType'),
('aiida.orm', 'Node', 'Node'),
('aiida.orm', 'ProcessNode', 'ProcessNode'),
('aiida.orm', 'CalculationNode', 'CalculationNode'),
Expand Down
4 changes: 2 additions & 2 deletions aiida/orm/nodes/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,14 @@ def set_source(self, source):
self.source = source

@property
def created_by(self):
def creator(self):
"""Return the creator of this node or None if it does not exist.

:return: the creating node or None
"""
inputs = self.get_incoming(link_type=LinkType.CREATE)
if inputs:
return inputs.first()
return inputs.first().node

return None

Expand Down
23 changes: 0 additions & 23 deletions aiida/orm/nodes/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
from aiida.orm.utils.links import LinkManager, LinkTriple
from aiida.orm.utils.repository import Repository
from aiida.orm.utils.node import AbstractNodeMeta, clean_value
from aiida.orm.utils.managers import NodeInputManager, NodeOutputManager

from ..comments import Comment
from ..computers import Computer
Expand Down Expand Up @@ -973,28 +972,6 @@ def has_cached_links(self):
"""
return bool(self._incoming_cache)

@property
def out(self):
"""Return an instance of `NodeOutputManager`

The `NodeOutputManager` allows you to easily explore the nodes that have outgoing links from this node.
The outgoing nodes are reachable by their link labels which are attributes of the manager.

:return: `NodeOutputManager`
"""
return NodeOutputManager(self)

@property
def inp(self):
"""Return an instance of `NodeInputManager`

The `NodeInputManager` allows you to easily explore the nodes that have incoming links to this node.
The incoming nodes are reachable by their link labels which are attributes of the manager.

:return: `NodeInputManager`
"""
return NodeInputManager(self)

def store_all(self, with_transaction=True, use_cache=None):
"""Store the node, together with all input links.

Expand Down
27 changes: 27 additions & 0 deletions aiida/orm/nodes/process/calculation/calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"""Module with `Node` sub class for calculation processes."""
from __future__ import absolute_import

from aiida.common.links import LinkType
from aiida.orm.utils.managers import NodeLinksManager

from ..process import ProcessNode

__all__ = ('CalculationNode',)
Expand All @@ -16,3 +19,27 @@ class CalculationNode(ProcessNode):
# Calculation nodes are storable
_storable = True
_unstorable_message = 'storing for this node has been disabled'

@property
def inputs(self):
"""Return an instance of `NodeLinksManager` to manage incoming INPUT_CALC links

The returned Manager allows you to easily explore the nodes connected to this node
via an incoming INPUT_CALC link.
The incoming nodes are reachable by their link labels which are attributes of the manager.

:return: `NodeLinksManager`
"""
return NodeLinksManager(node=self, link_type=LinkType.INPUT_CALC, incoming=True)

@property
def outputs(self):
"""Return an instance of `NodeLinksManager` to manage outgoing CREATE links

The returned Manager allows you to easily explore the nodes connected to this node
via an outgoing CREATE link.
The outgoing nodes are reachable by their link labels which are attributes of the manager.

:return: `NodeLinksManager`
"""
return NodeLinksManager(node=self, link_type=LinkType.CREATE, incoming=False)
8 changes: 4 additions & 4 deletions aiida/orm/nodes/process/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,15 +406,15 @@ def called_descendants(self):
return descendants

@property
def called_by(self):
def caller(self):
"""
Return the process node that called this process node, or None if it does not have a caller

:returns: process node that called this process node instance or None
"""
called_by = self.get_incoming(link_type=(LinkType.CALL_CALC, LinkType.CALL_WORK))
if called_by:
return called_by.first()
caller = self.get_incoming(link_type=(LinkType.CALL_CALC, LinkType.CALL_WORK))
if caller:
return caller.first().node

return None

Expand Down
Loading