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

Implement the usage of plum PortNamespaces #1099

Merged
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 aiida/backends/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
'work.futures': ['aiida.backends.tests.work.test_futures'],
'work.persistence': ['aiida.backends.tests.work.persistence'],
'work.process': ['aiida.backends.tests.work.process'],
'work.processSpec': ['aiida.backends.tests.work.processSpec'],
'work.process_spec': ['aiida.backends.tests.work.test_process_spec'],
'work.rmq': ['aiida.backends.tests.work.test_rmq'],
'work.run': ['aiida.backends.tests.work.run'],
'work.runners': ['aiida.backends.tests.work.test_runners'],
Expand Down
84 changes: 81 additions & 3 deletions aiida/backends/tests/work/job_processes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,49 @@

from aiida import work
from aiida.backends.testbase import AiidaTestCase
from aiida.common.utils import classproperty
from aiida.orm.data.base import Int
from aiida.orm.calculation.job.simpleplugins.templatereplacer import TemplatereplacerCalculation
from aiida.work.class_loader import ClassLoader
from aiida.work.job_processes import JobProcess

from . import utils

Job = TemplatereplacerCalculation.process()
class AdditionalParameterCalculation(TemplatereplacerCalculation):
"""
Subclass of TemplatereplacerCalculation that also defines a use method
with an additional parameter
"""

@classproperty
def _use_methods(cls):
retdict = TemplatereplacerCalculation._use_methods
retdict.update({
'pseudo': {
'valid_types': Int,
'additional_parameter': "kind",
'linkname': cls._get_linkname_pseudo,
'docstring': (''),
},
})
return retdict

@classmethod
def _get_linkname_pseudo(cls, kind):
"""
Create the linkname based on the additional parameter
"""
if isinstance(kind, (tuple, list)):
suffix_string = '_'.join(kind)
elif isinstance(kind, basestring):
suffix_string = kind
else:
raise TypeError('invalid additional parameter type')

return '{}_{}'.format('pseudo', suffix_string)

class TestJobProcess(AiidaTestCase):

def setUp(self):
super(TestJobProcess, self).setUp()
self.assertEquals(len(work.ProcessStack.stack()), 0)
Expand Down Expand Up @@ -54,7 +87,8 @@ def test_job_process_set_label_and_description(self):
'_label': label,
'_description': description
}
job = Job(inputs)
process = TemplatereplacerCalculation.process()
job = process(inputs)

self.assertEquals(job.calc.label, label)
self.assertEquals(job.calc.description, description)
Expand All @@ -76,4 +110,48 @@ def test_job_process_set_none(self):
'_description': None
}

Job(inputs)
process = TemplatereplacerCalculation.process()
job = process(inputs)

class TestAdditionalParameterJobProcess(AiidaTestCase):

def setUp(self):
super(TestAdditionalParameterJobProcess, self).setUp()
self.assertEquals(len(work.ProcessStack.stack()), 0)
self.runner = utils.create_test_runner()

def tearDown(self):
super(TestAdditionalParameterJobProcess, self).tearDown()
self.assertEquals(len(work.ProcessStack.stack()), 0)
self.runner.close()
self.runner = None
work.set_runner(None)

def test_class_loader(self):
cl = ClassLoader()
AdditionalParameterProcess = JobProcess.build(AdditionalParameterCalculation)

def test_job_process_with_additional_parameter(self):
"""
Verify that the additional parameter use_method 'pseudo' is supported
"""
label = 'test_label'
description = 'test_description'
inputs = {
'_options': {
'computer': self.computer,
'resources': {
'num_machines': 1,
'num_mpiprocs_per_machine': 1
},
'max_wallclock_seconds': 10,
},
'pseudo': {
'a': Int(1),
'b': Int(2),
},
'_label': label,
'_description': description
}
process = AdditionalParameterCalculation.process()
job = process(inputs)
92 changes: 64 additions & 28 deletions aiida/backends/tests/work/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,56 @@
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################


import threading

import aiida.work.utils as util
from plum.utils import AttributesFrozendict
from aiida import work
from aiida.backends.testbase import AiidaTestCase
from aiida.common.lang import override
from aiida.orm import load_node
from aiida.orm.data.base import Int
from aiida.orm.data.frozendict import FrozenDict
from aiida.work.test_utils import DummyProcess, BadOutput
from aiida import work
from aiida.work.launch import run, run_get_pid
from aiida.work import utils
from aiida.work import test_utils

class TestProcessNamespace(AiidaTestCase):

def setUp(self):
super(TestProcessNamespace, self).setUp()
self.assertEquals(len(utils.ProcessStack.stack()), 0)

def tearDown(self):
super(TestProcessNamespace, self).tearDown()
self.assertEquals(len(utils.ProcessStack.stack()), 0)

def test_namespaced_process(self):
"""
Test that inputs in nested namespaces are properly validated and the link labels
are properly formatted by connecting the namespaces with underscores
"""
class NameSpacedProcess(work.Process):

@classmethod
def define(cls, spec):
super(NameSpacedProcess, cls).define(spec)
spec.input('some.name.space.a', valid_type=Int)

proc = NameSpacedProcess(inputs={'some': {'name': {'space': {'a': Int(5)}}}})

# Test that the namespaced inputs are AttributesFrozenDicts
self.assertIsInstance(proc.inputs, AttributesFrozendict)
self.assertIsInstance(proc.inputs.some, AttributesFrozendict)
self.assertIsInstance(proc.inputs.some.name, AttributesFrozendict)
self.assertIsInstance(proc.inputs.some.name.space, AttributesFrozendict)

# Test that the input node is in the inputs of the process
input_node = proc.inputs.some.name.space.a
self.assertTrue(isinstance(input_node, Int))
self.assertEquals(input_node.value, 5)

# Check that the link of the WorkCalculation node has the correct link name
self.assertTrue('some_name_space_a' in proc.calc.get_inputs_dict())
self.assertEquals(proc.calc.get_inputs_dict()['some_name_space_a'], 5)


class ProcessStackTest(work.Process):
Expand All @@ -44,26 +81,26 @@ class TestProcess(AiidaTestCase):
def setUp(self):
super(TestProcess, self).setUp()

self.assertEquals(len(util.ProcessStack.stack()), 0)
self.assertEquals(len(utils.ProcessStack.stack()), 0)

def tearDown(self):
super(TestProcess, self).tearDown()

self.assertEquals(len(util.ProcessStack.stack()), 0)
self.assertEquals(len(utils.ProcessStack.stack()), 0)

def test_process_stack(self):
run(ProcessStackTest)
work.launch.run(ProcessStackTest)

def test_inputs(self):
with self.assertRaises(TypeError):
run(BadOutput)
work.launch.run(test_utils.BadOutput)

def test_input_link_creation(self):
dummy_inputs = ["1", "2", "3", "4"]

inputs = {l: Int(l) for l in dummy_inputs}
inputs['_store_provenance'] = True
p = DummyProcess(inputs)
p = test_utils.DummyProcess(inputs)

for label, value in p._calc.get_inputs_dict().iteritems():
self.assertTrue(label in inputs)
Expand All @@ -75,63 +112,62 @@ def test_input_link_creation(self):

def test_none_input(self):
# Check that if we pass no input the process runs fine
run(DummyProcess)
work.launch.run(test_utils.DummyProcess)

def test_seal(self):
pid = run_get_pid(DummyProcess).pid
pid = work.launch.run_get_pid(test_utils.DummyProcess).pid
self.assertTrue(load_node(pk=pid).is_sealed)

def test_description(self):
dp = DummyProcess(inputs={'_description': "Rockin' process"})
dp = test_utils.DummyProcess(inputs={'_description': "Rockin' process"})
self.assertEquals(dp.calc.description, "Rockin' process")

with self.assertRaises(ValueError):
DummyProcess(inputs={'_description': 5})
test_utils.DummyProcess(inputs={'_description': 5})

def test_label(self):
dp = DummyProcess(inputs={'_label': 'My label'})
dp = test_utils.DummyProcess(inputs={'_label': 'My label'})
self.assertEquals(dp.calc.label, 'My label')

with self.assertRaises(ValueError):
DummyProcess(inputs={'_label': 5})
test_utils.DummyProcess(inputs={'_label': 5})

def test_work_calc_finish(self):
p = DummyProcess()
p = test_utils.DummyProcess()
self.assertFalse(p.calc.has_finished_ok())
run(p)
work.launch.run(p)
self.assertTrue(p.calc.has_finished_ok())

def test_calculation_input(self):
@work.workfunction
def simple_wf():
return {'a': Int(6), 'b': Int(7)}

outputs, pid = run_get_pid(simple_wf)
outputs, pid = work.launch.run_get_pid(simple_wf)
calc = load_node(pid)

dp = DummyProcess(inputs={'calc': calc})
run(dp)
dp = test_utils.DummyProcess(inputs={'calc': calc})
work.launch.run(dp)

input_calc = dp.calc.get_inputs_dict()['calc']
self.assertTrue(isinstance(input_calc, FrozenDict))
self.assertEqual(input_calc['a'], outputs['a'])

def test_save_instance_state(self):
proc = DummyProcess()
proc = test_utils.DummyProcess()
# Save the instance state
bundle = work.Bundle(proc)

proc2 = bundle.unbundle()


class TestFunctionProcess(AiidaTestCase):
def test_fixed_inputs(self):
def wf(a, b, c):
return {'a': a, 'b': b, 'c': c}

inputs = {'a': Int(4), 'b': Int(5), 'c': Int(6)}
function_process_class = work.FunctionProcess.build(wf)
self.assertEqual(run(function_process_class, **inputs), inputs)
self.assertEqual(work.launch.run(function_process_class, **inputs), inputs)

def test_kwargs(self):
def wf_with_kwargs(**kwargs):
Expand All @@ -147,13 +183,13 @@ def wf_fixed_args(a):
inputs = {'a': a}

function_process_class = work.FunctionProcess.build(wf_with_kwargs)
outs = run(function_process_class, **inputs)
outs = work.launch.run(function_process_class, **inputs)
self.assertEqual(outs, inputs)

function_process_class = work.FunctionProcess.build(wf_without_kwargs)
with self.assertRaises(ValueError):
run(function_process_class, **inputs)
work.launch.run(function_process_class, **inputs)

function_process_class = work.FunctionProcess.build(wf_fixed_args)
outs = run(function_process_class, **inputs)
outs = work.launch.run(function_process_class, **inputs)
self.assertEqual(outs, inputs)
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,21 @@ def test_dynamic_input(self):

n = Node()
d = Data()
port = self.spec.get_dynamic_input()
self.assertFalse(port.validate("foo")[0])
self.assertFalse(port.validate(5)[0])
self.assertFalse(port.validate(n)[0])
self.assertTrue(port.validate(d)[0])
self.assertFalse(self.spec.validate_inputs({'key': 'foo'})[0])
self.assertFalse(self.spec.validate_inputs({'key': 5})[0])
self.assertFalse(self.spec.validate_inputs({'key': n})[0])
self.assertTrue(self.spec.validate_inputs({'key': d})[0])

def test_dynamic_output(self):
from aiida.orm import Node
from aiida.orm.data import Data

n = Node()
d = Data()
port = self.spec.get_dynamic_output()
self.assertFalse(port.validate("foo")[0])
self.assertFalse(port.validate(5)[0])
self.assertFalse(port.validate(n)[0])
self.assertTrue(port.validate(d)[0])
self.assertFalse(self.spec.validate_inputs({'key': 'foo'})[0])
self.assertFalse(self.spec.validate_inputs({'key': 5})[0])
self.assertFalse(self.spec.validate_inputs({'key': n})[0])
self.assertTrue(self.spec.validate_inputs({'key': d})[0])

def _test_template(self, template):
template.a = 2
Expand Down
Loading