def test_add_function(self):

        code = "def foo(a, b):\n" "    x, y = a, b\n" "    return x, y\n" "c, d = foo(1, 2)"

        block = Block(code)
        info = find_functions(block.ast)
        func = FunctionCall.from_ast(block.sub_blocks[-1].ast, info)
        model = ExecutionModel()
        model.add_function(func)

        assert len(model.sorted_statements) == 1

        desired = "\ndef foo(a, b): \n    x, y = (a, b)\n    return x, y\n\nx_, y_ = foo(1, 2)"
        self.assertEqual(desired, model.code)
Example #2
0
    def __init__(self, code=None, shared_context=None, *args, **kwargs):
        super(Experiment, self).__init__(*args, **kwargs)

        if code is None:
            self.exec_model = ExecutionModel()
        else:
            self.exec_model = ExecutionModel.from_code(code)

        self.controller = BlockGraphController(execution_model = self.exec_model)
        self.canvas = BlockCanvas(graph_controller=self.controller)
        self.controller.canvas = self.canvas

        self._shared_context = shared_context
        self._local_context = DataContext(name = self._LOCAL_NAME_TEMPLATE())
        self._update_exec_context()
Example #3
0
    def __init__(self, code=None, shared_context=None, *args, **kwargs):
        super(Experiment, self).__init__(*args, **kwargs)

        if code is None:
            self.exec_model = ExecutionModel()
        else:
            self.exec_model = ExecutionModel.from_code(code)

        self.controller = BlockGraphController(execution_model=self.exec_model)
        self.canvas = BlockCanvas(graph_controller=self.controller)
        self.controller.canvas = self.canvas

        self._shared_context = shared_context
        self._local_context = DataContext(name=self._LOCAL_NAME_TEMPLATE())
        self._update_exec_context()
    def test_imported_func_code(self):
        code = "from blockcanvas.debug.my_operator import mul\n" \
               "c = mul(a, b)\n" \

        desired = "from blockcanvas.debug.my_operator import mul\n\nc = mul(a, b)"
        model = ExecutionModel.from_code(code)
        self.assertEqual(model.code, desired)
    def test_code_from_local_def(self):

        code = "def foo(a, b):\n" "\treturn b, a\n" "x,y = foo(1,2)\n"

        desired = "\ndef foo(a, b): \n    return b, a\n\nx, y = foo(1, 2)"
        model = ExecutionModel.from_code(code)
        self.assertEqual(desired, model.code)
    def test_import_func_with_const(self):

        code = "from blockcanvas.debug.my_operator import mul\n" \
               "a = mul(1.0, 2.0)\n" \

        desired = "from blockcanvas.debug.my_operator import mul\n\na = mul(1.0, 2.0)"
        model = ExecutionModel.from_code(code)
        self.assertEqual(model.code, desired)
    def test_add_function(self):

        code = "def foo(a, b):\n" \
                "    x, y = a, b\n" \
                "    return x, y\n" \
                "c, d = foo(1, 2)"

        block = Block(code)
        info = find_functions(block.ast)
        func = FunctionCall.from_ast(block.sub_blocks[-1].ast, info)
        model = ExecutionModel()
        model.add_function(func)

        assert len(model.sorted_statements) == 1

        desired = '\ndef foo(a, b): \n    x, y = (a, b)\n    return x, y\n\nx_, y_ = foo(1, 2)'
        self.assertEqual(desired, model.code)
Example #8
0
    def load_code_from_file(self, filename):

        # Clear out the old references
        self.canvas = self.controller = self.exec_model = None

        self.exec_model = ExecutionModel.from_file(filename)
        self.controller = BlockGraphController(execution_model=self.exec_model)
        self.canvas = BlockCanvas(graph_controller=self.controller)
        self.controller.canvas = self.canvas
Example #9
0
 def load_code_from_file(self,filename):
     
     # Clear out the old references
     self.canvas = self.controller = self.exec_model = None
     
     self.exec_model = ExecutionModel.from_file(filename)
     self.controller = BlockGraphController(execution_model = self.exec_model)
     self.canvas = BlockCanvas(graph_controller=self.controller)
     self.controller.canvas = self.canvas
    def test_remove_function(self):

        code = "def foo(a, b):\n" "    x, y = a, b\n" "    return x, y\n" "c, d = foo(1, 2)\n" "e, f = foo(3, 4)"

        block = Block(code)
        info = find_functions(block.ast)
        foo1_func = FunctionCall.from_ast(block.sub_blocks[-1].ast, info)
        foo2_func = FunctionCall.from_ast(block.sub_blocks[-2].ast, info)
        assert foo1_func != foo2_func

        model = ExecutionModel()
        model.add_function(foo1_func)
        model.add_function(foo2_func)

        model.remove_function(foo2_func)

        assert not (foo2_func in model.statements)
        assert foo1_func in model.statements
    def test_code_from_local_def(self):

        code = "def foo(a, b):\n" \
               "\treturn b, a\n" \
               "x,y = foo(1,2)\n"

        desired = '\ndef foo(a, b): \n    return b, a\n\nx, y = foo(1, 2)'
        model = ExecutionModel.from_code(code)
        self.assertEqual(desired, model.code)
    def test_merge_statements(self):

        code = "from blockcanvas.debug.my_operator import add, mul\n" \
               "a = mul(1.0,2.0)\n" \
               "b = add(1.0,3.0)\n" \

        model = ExecutionModel.from_code(code)
        model.merge_statements(
            ids=[model.statements[0].uuid, model.statements[1].uuid])
    def setUp(self):
        unittest.TestCase.setUp(self)

        self.code = 'from blockcanvas.debug.my_operator import mul, add\n' \
                    'a = mul(1,2)\n' \
                    'b = mul(5,6)\n' \
                    'c = add(a,b)'
        self.model = ExecutionModel.from_code(self.code)
        self.controller = BlockGraphController(execution_model=self.model)
        self.file_path = 'temp.pickle'
    def setUp(self):
        unittest.TestCase.setUp(self)

        self.code = 'from blockcanvas.debug.my_operator import mul, add\n' \
                    'a = mul(1,2)\n' \
                    'b = mul(5,6)\n' \
                    'c = add(a,b)'
        self.model = ExecutionModel.from_code(self.code)
        self.controller = BlockGraphController(execution_model=self.model)
        self.file_path = 'temp.pickle'
 def setUp(self):
     self.exec_code = (
         "from blockcanvas.debug.my_operator import add, mul\n" "c = mul(a,b)\n" "d = add(c,b)\n" "f = mul(d,e)\n"
     )
     self.exec_model = ExecutionModel.from_code(self.exec_code)
     self.inputs = {}
     for stmt in self.exec_model.statements:
         for iv in stmt.inputs:
             self.inputs[iv.binding] = iv
     self.simple_context = dict(a=2, b=4, e=5)
    def test_restricted(self):
        code = "from blockcanvas.debug.my_operator import add, mul\n" "c = mul(a,b)\n" "d = add(c,b)\n" "f = mul(d,e)\n"

        model = ExecutionModel.from_code(code)

        not_really_restricted = model.restricted()
        self.assertEqual(not_really_restricted.sorted_statements, model.sorted_statements)

        cstmt, dstmt, fstmt = model.statements
        self.assertEqual(model.restricted(inputs=["e"]).sorted_statements, [fstmt])
        self.assertEqual(model.restricted(outputs=["d"]).sorted_statements, [cstmt, dstmt])
        self.assertEqual(model.restricted(inputs=["b"], outputs=["c"]).sorted_statements, [cstmt])
    def test_local_def_code(self):

        code = "from blockcanvas.debug.my_operator import mul\n" \
               "def foo(a):\n" \
               "\tb = a\n" \
               "\treturn b\n" \
               "a = mul(1.0, 2.0)\n" \
               "b = foo(a)\n"

        desired = 'def foo(a): \n    b = a\n    return b\n'
        model = ExecutionModel.from_code(code)
        assert model.code.find(desired) > 0
 def setUp(self):
     self.exec_code = (
         "from blockcanvas.debug.my_operator import add, mul\n"
         "c = mul(a,b)\n"
         "d = add(c,b)\n"
         "f = mul(d,e)\n")
     self.exec_model = ExecutionModel.from_code(self.exec_code)
     self.inputs = {}
     for stmt in self.exec_model.statements:
         for iv in stmt.inputs:
             self.inputs[iv.binding] = iv
     self.simple_context = dict(a=2, b=4, e=5)
    def test_change_binding(self):
        """ Does a change in the binding update the code from the ExecutionModel
            correctly?
        """

        code = "from blockcanvas.debug.my_operator import add\n" "a = add(1, 2)\n"

        model = ExecutionModel.from_code(code)
        func_call = model.statements[0]
        func_call.inputs[0].binding = "2"

        desired = "from blockcanvas.debug.my_operator import add\n\n" "a = add(2, 2)"
        self.assertEqual(desired, model.code)
    def test_local_def_code(self):

        code = (
            "from blockcanvas.debug.my_operator import mul\n"
            "def foo(a):\n"
            "\tb = a\n"
            "\treturn b\n"
            "a = mul(1.0, 2.0)\n"
            "b = foo(a)\n"
        )

        desired = "def foo(a): \n    b = a\n    return b\n"
        model = ExecutionModel.from_code(code)
        assert model.code.find(desired) > 0
Example #21
0
    def load_from_config(self, config, dirname, project=None):
        """ Loads the experiment.  The existing state of the experiment is
        completely modified.

        Parameters
        ----------
        config: a dict-like object
            The keys should correspond to the configspec for experiments as
            defined in project_config_spec.

        dirname: a string
            the absolute path to the subdirectory of the project that
            holds the experiment's saved data

        project: Project instance
            If provided, the project is used to hook up references to
            the shared context.
        """

        join = os.path.join
        self.name = config.get("name", "")

        if "code_file" in config:

            # Clear out the old references
            self.canvas = self.controller = self.exec_model = None

            self.exec_model = ExecutionModel.from_file(
                join(dirname, config["code_file"]))
            self.controller = BlockGraphController(
                execution_model=self.exec_model)
            self.canvas = BlockCanvas(graph_controller=self.controller)
            self.controller.canvas = self.canvas

        if "layout_file" in config:
            self.canvas.load_layout(join(dirname, config["layout_file"]))

        if "local_context" in config:
            self._local_context = DataContext.load(
                join(dirname, config["local_context"]))

        shared_context = None
        if project is not None:
            name = config.get("shared_context")
            if name != "":
                shared_context = project.find_context(name)

        self._shared_context = shared_context
        self._update_exec_context()
    def test_change_binding(self):
        """ Does a change in the binding update the code from the ExecutionModel
            correctly?
        """

        code = "from blockcanvas.debug.my_operator import add\n" \
               "a = add(1, 2)\n"

        model = ExecutionModel.from_code(code)
        func_call = model.statements[0]
        func_call.inputs[0].binding = '2'

        desired = "from blockcanvas.debug.my_operator import add\n\n" \
                  "a = add(2, 2)"
        self.assertEqual(desired, model.code)
    def test_imported_func_multi_line(self):
        """ Right now, we can't ensure the order of the import statements nor
            can we consolidate imports from the same locations. """

        code = "from blockcanvas.debug.my_operator import add, mul\n" \
               "a = add(1.0,2.0)\n" \
               "b = add(a,3.0)\n" \
               "c = mul(b,b)\n" \

        desired = ["a = add(1.0, 2.0)", "b = add(a, 3.0)", "c = mul(b, b)"]
        model = ExecutionModel.from_code(code)
        codelines = model.code.split('\n')
        for line in desired:
            assert line in codelines

        import_line = codelines[0]
        assert import_line.find('add') and import_line.find('mul')
Example #24
0
    def load_from_config(self, config, dirname, project=None):
        """ Loads the experiment.  The existing state of the experiment is
        completely modified.

        Parameters
        ----------
        config: a dict-like object
            The keys should correspond to the configspec for experiments as
            defined in project_config_spec.

        dirname: a string
            the absolute path to the subdirectory of the project that
            holds the experiment's saved data

        project: Project instance
            If provided, the project is used to hook up references to
            the shared context.
        """

        join = os.path.join
        self.name = config.get("name", "")

        if "code_file" in config:

            # Clear out the old references
            self.canvas = self.controller = self.exec_model = None

            self.exec_model = ExecutionModel.from_file(join(dirname, config["code_file"]))
            self.controller = BlockGraphController(execution_model = self.exec_model)
            self.canvas = BlockCanvas(graph_controller=self.controller)
            self.controller.canvas = self.canvas

        if "layout_file" in config:
            self.canvas.load_layout(join(dirname, config["layout_file"]))

        if "local_context" in config:
            self._local_context = DataContext.load(join(dirname, config["local_context"]))

        shared_context = None
        if project is not None:
            name = config.get("shared_context")
            if name != "":
                shared_context = project.find_context(name)

        self._shared_context = shared_context
        self._update_exec_context()
    def test_imported_func_multi_line(self):
        """ Right now, we can't ensure the order of the import statements nor
            can we consolidate imports from the same locations. """

        code = (
            "from blockcanvas.debug.my_operator import add, mul\n"
            "a = add(1.0,2.0)\n"
            "b = add(a,3.0)\n"
            "c = mul(b,b)\n"
        )
        desired = ["a = add(1.0, 2.0)", "b = add(a, 3.0)", "c = mul(b, b)"]
        model = ExecutionModel.from_code(code)
        codelines = model.code.split("\n")
        for line in desired:
            assert line in codelines

        import_line = codelines[0]
        assert import_line.find("add") and import_line.find("mul")
    def test_restricted(self):
        code = "from blockcanvas.debug.my_operator import add, mul\n" \
               "c = mul(a,b)\n" \
               "d = add(c,b)\n" \
               "f = mul(d,e)\n"

        model = ExecutionModel.from_code(code)

        not_really_restricted = model.restricted()
        self.assertEqual(not_really_restricted.sorted_statements,
                         model.sorted_statements)

        cstmt, dstmt, fstmt = model.statements
        self.assertEqual(
            model.restricted(inputs=['e']).sorted_statements, [fstmt])
        self.assertEqual(
            model.restricted(outputs=['d']).sorted_statements, [cstmt, dstmt])
        self.assertEqual(
            model.restricted(inputs=['b'], outputs=['c']).sorted_statements,
            [cstmt])
    def test_remove_function(self):

        code = "def foo(a, b):\n" \
                "    x, y = a, b\n" \
                "    return x, y\n" \
                "c, d = foo(1, 2)\n" \
                "e, f = foo(3, 4)"

        block = Block(code)
        info = find_functions(block.ast)
        foo1_func = FunctionCall.from_ast(block.sub_blocks[-1].ast, info)
        foo2_func = FunctionCall.from_ast(block.sub_blocks[-2].ast, info)
        assert foo1_func != foo2_func

        model = ExecutionModel()
        model.add_function(foo1_func)
        model.add_function(foo2_func)

        model.remove_function(foo2_func)

        assert not (foo2_func in model.statements)
        assert foo1_func in model.statements
    def test_unmerge_statements(self):

        code = "from blockcanvas.debug.my_operator import add, mul\n" "a = mul(1.0,2.0)\n" "b = add(1.0,3.0)\n"
        model = ExecutionModel.from_code(code)
        model.merge_statements(ids=[model.statements[0].uuid, model.statements[1].uuid])
        model.unmerge_statements(model.statements[0].uuid)
    def test_import_func_with_const(self):

        code = "from blockcanvas.debug.my_operator import mul\n" "a = mul(1.0, 2.0)\n"
        desired = "from blockcanvas.debug.my_operator import mul\n\na = mul(1.0, 2.0)"
        model = ExecutionModel.from_code(code)
        self.assertEqual(model.code, desired)
Example #30
0
class Experiment(HasTraits):
    """
    An Experiment represents a particular workflow and its associated data.
    It is associated with a particular context, but any data it produces
    is placed into a shadowing "local" context.

    It encapsulates an execution model, its associated canvas, and the
    local context.
    """

    # The name of the experiment.
    name = String()

    # The Execution Model object
    exec_model = Instance(ExecutionModel)

    # The Block Canvas
    canvas = Instance(BlockCanvas)

    # The controller between the canvas and execution model
    controller = Instance(BlockGraphController)

    # The execution context
    context = Instance(ExecutingContext)

    # A reference to a context that might be shared with other experiments.
    # This is usually a context from the project's list of contexts.  If None,
    # then all of the data in the experiment is kept "locally".
    #
    # Note: For most purposes, you should use the **context** attribute
    # and not this one, unless you really know what you are doing.
    shared_context = Property(Instance(IListenableContext))


    #---------------------------------------------------------------------
    # Private Traits
    #---------------------------------------------------------------------

    # A shadow trait for **shared_context** property
    _shared_context = Instance(IListenableContext, adapt='yes',
        rich_compare=False)

    # The context in which all of our execution model's generated values are
    # stored.  This context is exposed as part of self.context.  This remains
    # constant even as the _shared_context gets changed.
    _local_context = Instance(IListenableContext, rich_compare=False)

    # Class-level generator for the name of the local context; takes
    # **self** as an argument.
    _LOCAL_NAME_TEMPLATE = lambda x: "%s_local_%d" % (x.name, id(x))

    #---------------------------------------------------------------------
    # Public methods
    #---------------------------------------------------------------------

    def __init__(self, code=None, shared_context=None, *args, **kwargs):
        super(Experiment, self).__init__(*args, **kwargs)

        if code is None:
            self.exec_model = ExecutionModel()
        else:
            self.exec_model = ExecutionModel.from_code(code)

        self.controller = BlockGraphController(execution_model = self.exec_model)
        self.canvas = BlockCanvas(graph_controller=self.controller)
        self.controller.canvas = self.canvas

        self._shared_context = shared_context
        self._local_context = DataContext(name = self._LOCAL_NAME_TEMPLATE())
        self._update_exec_context()

    #---------------------------------------------------------------------
    # Persistence
    #---------------------------------------------------------------------


    def load_code_from_file(self,filename):
        
        # Clear out the old references
        self.canvas = self.controller = self.exec_model = None
        
        self.exec_model = ExecutionModel.from_file(filename)
        self.controller = BlockGraphController(execution_model = self.exec_model)
        self.canvas = BlockCanvas(graph_controller=self.controller)
        self.controller.canvas = self.canvas


    def load_from_config(self, config, dirname, project=None):
        """ Loads the experiment.  The existing state of the experiment is
        completely modified.

        Parameters
        ----------
        config: a dict-like object
            The keys should correspond to the configspec for experiments as
            defined in project_config_spec.

        dirname: a string
            the absolute path to the subdirectory of the project that
            holds the experiment's saved data

        project: Project instance
            If provided, the project is used to hook up references to
            the shared context.
        """

        join = os.path.join
        self.name = config.get("name", "")

        if "code_file" in config:

            # Clear out the old references
            self.canvas = self.controller = self.exec_model = None

            self.exec_model = ExecutionModel.from_file(join(dirname, config["code_file"]))
            self.controller = BlockGraphController(execution_model = self.exec_model)
            self.canvas = BlockCanvas(graph_controller=self.controller)
            self.controller.canvas = self.canvas

        if "layout_file" in config:
            self.canvas.load_layout(join(dirname, config["layout_file"]))

        if "local_context" in config:
            self._local_context = DataContext.load(join(dirname, config["local_context"]))

        shared_context = None
        if project is not None:
            name = config.get("shared_context")
            if name != "":
                shared_context = project.find_context(name)

        self._shared_context = shared_context
        self._update_exec_context()


    def save(self, basename, dirname):
        """ Saves the experiment into a directory.

        Parameters
        ----------
        basename: string
            the name of the project directory
        dirname: string
            the name of the experiment's subdirectory

        Returns
        -------
        A dict representing the saved state for this object.  All path
        references in the dict will be relative to the given root directory.
        """

        # Make the directory using the absolute path.
        fullpath = join(basename, dirname)
        if not os.path.isdir(fullpath):
            os.mkdir(fullpath)

        config = {}
        config["name"] = self.name
        config["save_dir"] = dirname

        # Save out the canvas
        config["layout_file"] = "layout.dat"
        self.canvas.save_layout(join(fullpath, "layout.dat"))

        # Save the execution model's code
        config["code_file"] = "code.txt"
        f = file(join(fullpath, "code.txt"), "w")
        try:
            f.write(self.exec_model.code)
        finally:
            f.close()

        # Clean context
        self.exec_model._clean_old_results_from_context(self.context.subcontext) 

        # Save the local context
        config["local_context"] = "local_context.pickle"
        self._local_context.save(join(fullpath, "local_context.pickle"))

        # Store the name of the shared context
        if self._shared_context is not None:
            config["shared_context"] = self._shared_context.name

        return config

    def save_script(self,filename):
        """ Save the execution model code as script. """
        f = file(filename, "w")
        try:
            f.write(self.exec_model.code)
        finally:
            f.close()
    
    def export_as_function(self,filename,func_name, mode="w"):
        """ Export this experiment as a function for batch application. 
        
        It puts together the model (i.e. code) and the parameters 
        (i.e. context) to create a self-contained function to be used 
        in batch application.  
        """
        imports_and_locals = self.exec_model.imports_and_locals
        sorted_statements = self.exec_model.sorted_statements
        context = self.context

        # Clean context
        self.exec_model._clean_old_results_from_context(context.subcontext) 
       
        export_as_function(filename, func_name, \
                           imports_and_locals, sorted_statements, context, \
                           reserved_inputs=[], \
                           reserved_outputs=[], \
                           mode=mode)

    def export_as_script(self,filename, mode="w"):
        """ Export this experiment as a script for batch application. 
        
        It puts together the model (i.e. code) and the parameters 
        (i.e. context) to create a self-contained script to be used 
        in batch application. 
        """        
        imports_and_locals = self.exec_model.imports_and_locals
        sorted_statements = self.exec_model.sorted_statements
        context = self.context
        
        # Clean context
        self.exec_model._clean_old_results_from_context(context.subcontext) 
        
        export_as_script(filename, \
                         imports_and_locals, sorted_statements, context, \
                         reserved_inputs=[], \
                         reserved_outputs=[], \
                         mode=mode)

    #---------------------------------------------------------------------
    # Trait Event Handlers
    #---------------------------------------------------------------------

    @on_trait_change('exec_model')
    def _exec_model_changed(self, name, old, new):
        """ Propagate the change to the objects under this one.
        """
        if self.controller is not None:
            self.controller.execution_model = new
        if self.context is not None:
            self.context.executable = new


    #---------------------------------------------------------------------
    # Property getters/setters
    #---------------------------------------------------------------------

    def _get_shared_context(self):
        return self._shared_context

    def _set_shared_context(self, newcontext):
        subcontexts = self.context.subcontext.subcontexts
        if self._shared_context is not None:
            assert(self._shared_context in subcontexts)
            if newcontext is None:
                subcontexts.remove(self._shared_context)
                self._shared_context = None
            else:
                # Swap out the new context for the old, in-place
                ndx = subcontexts.index(self._shared_context)
                subcontexts[ndx] = newcontext
                self._shared_context = newcontext
        elif newcontext is not None:
            self._shared_context = newcontext
            subcontexts.append(newcontext)
        return


    def _update_exec_context(self):
        mc = MultiContext(
            # Put the function filter in front so we don't dirty up the data
            # context with function objects.
            FunctionFilterContext(name='functions'),
            self._local_context,
            name='multi',
        )

        if self._shared_context is not None:
            mc.subcontexts.append(self._shared_context)

        self.context = ExecutingContext(
            executable=self.exec_model,
            subcontext=mc,
            name='exec',
        )
 def test_imported_func_code(self):
     code = "from blockcanvas.debug.my_operator import mul\n" "c = mul(a, b)\n"
     desired = "from blockcanvas.debug.my_operator import mul\n\nc = mul(a, b)"
     model = ExecutionModel.from_code(code)
     self.assertEqual(model.code, desired)
Example #32
0
class Experiment(HasTraits):
    """
    An Experiment represents a particular workflow and its associated data.
    It is associated with a particular context, but any data it produces
    is placed into a shadowing "local" context.

    It encapsulates an execution model, its associated canvas, and the
    local context.
    """

    # The name of the experiment.
    name = String()

    # The Execution Model object
    exec_model = Instance(ExecutionModel)

    # The Block Canvas
    canvas = Instance(BlockCanvas)

    # The controller between the canvas and execution model
    controller = Instance(BlockGraphController)

    # The execution context
    context = Instance(ExecutingContext)

    # A reference to a context that might be shared with other experiments.
    # This is usually a context from the project's list of contexts.  If None,
    # then all of the data in the experiment is kept "locally".
    #
    # Note: For most purposes, you should use the **context** attribute
    # and not this one, unless you really know what you are doing.
    shared_context = Property(Instance(IListenableContext))

    #---------------------------------------------------------------------
    # Private Traits
    #---------------------------------------------------------------------

    # A shadow trait for **shared_context** property
    _shared_context = Instance(IListenableContext,
                               adapt='yes',
                               rich_compare=False)

    # The context in which all of our execution model's generated values are
    # stored.  This context is exposed as part of self.context.  This remains
    # constant even as the _shared_context gets changed.
    _local_context = Instance(IListenableContext, rich_compare=False)

    # Class-level generator for the name of the local context; takes
    # **self** as an argument.
    _LOCAL_NAME_TEMPLATE = lambda x: "%s_local_%d" % (x.name, id(x))

    #---------------------------------------------------------------------
    # Public methods
    #---------------------------------------------------------------------

    def __init__(self, code=None, shared_context=None, *args, **kwargs):
        super(Experiment, self).__init__(*args, **kwargs)

        if code is None:
            self.exec_model = ExecutionModel()
        else:
            self.exec_model = ExecutionModel.from_code(code)

        self.controller = BlockGraphController(execution_model=self.exec_model)
        self.canvas = BlockCanvas(graph_controller=self.controller)
        self.controller.canvas = self.canvas

        self._shared_context = shared_context
        self._local_context = DataContext(name=self._LOCAL_NAME_TEMPLATE())
        self._update_exec_context()

    #---------------------------------------------------------------------
    # Persistence
    #---------------------------------------------------------------------

    def load_code_from_file(self, filename):

        # Clear out the old references
        self.canvas = self.controller = self.exec_model = None

        self.exec_model = ExecutionModel.from_file(filename)
        self.controller = BlockGraphController(execution_model=self.exec_model)
        self.canvas = BlockCanvas(graph_controller=self.controller)
        self.controller.canvas = self.canvas

    def load_from_config(self, config, dirname, project=None):
        """ Loads the experiment.  The existing state of the experiment is
        completely modified.

        Parameters
        ----------
        config: a dict-like object
            The keys should correspond to the configspec for experiments as
            defined in project_config_spec.

        dirname: a string
            the absolute path to the subdirectory of the project that
            holds the experiment's saved data

        project: Project instance
            If provided, the project is used to hook up references to
            the shared context.
        """

        join = os.path.join
        self.name = config.get("name", "")

        if "code_file" in config:

            # Clear out the old references
            self.canvas = self.controller = self.exec_model = None

            self.exec_model = ExecutionModel.from_file(
                join(dirname, config["code_file"]))
            self.controller = BlockGraphController(
                execution_model=self.exec_model)
            self.canvas = BlockCanvas(graph_controller=self.controller)
            self.controller.canvas = self.canvas

        if "layout_file" in config:
            self.canvas.load_layout(join(dirname, config["layout_file"]))

        if "local_context" in config:
            self._local_context = DataContext.load(
                join(dirname, config["local_context"]))

        shared_context = None
        if project is not None:
            name = config.get("shared_context")
            if name != "":
                shared_context = project.find_context(name)

        self._shared_context = shared_context
        self._update_exec_context()

    def save(self, basename, dirname):
        """ Saves the experiment into a directory.

        Parameters
        ----------
        basename: string
            the name of the project directory
        dirname: string
            the name of the experiment's subdirectory

        Returns
        -------
        A dict representing the saved state for this object.  All path
        references in the dict will be relative to the given root directory.
        """

        # Make the directory using the absolute path.
        fullpath = join(basename, dirname)
        if not os.path.isdir(fullpath):
            os.mkdir(fullpath)

        config = {}
        config["name"] = self.name
        config["save_dir"] = dirname

        # Save out the canvas
        config["layout_file"] = "layout.dat"
        self.canvas.save_layout(join(fullpath, "layout.dat"))

        # Save the execution model's code
        config["code_file"] = "code.txt"
        f = file(join(fullpath, "code.txt"), "w")
        try:
            f.write(self.exec_model.code)
        finally:
            f.close()

        # Clean context
        self.exec_model._clean_old_results_from_context(
            self.context.subcontext)

        # Save the local context
        config["local_context"] = "local_context.pickle"
        self._local_context.save(join(fullpath, "local_context.pickle"))

        # Store the name of the shared context
        if self._shared_context is not None:
            config["shared_context"] = self._shared_context.name

        return config

    def save_script(self, filename):
        """ Save the execution model code as script. """
        f = file(filename, "w")
        try:
            f.write(self.exec_model.code)
        finally:
            f.close()

    def export_as_function(self, filename, func_name, mode="w"):
        """ Export this experiment as a function for batch application. 
        
        It puts together the model (i.e. code) and the parameters 
        (i.e. context) to create a self-contained function to be used 
        in batch application.  
        """
        imports_and_locals = self.exec_model.imports_and_locals
        sorted_statements = self.exec_model.sorted_statements
        context = self.context

        # Clean context
        self.exec_model._clean_old_results_from_context(context.subcontext)

        export_as_function(filename, func_name, \
                           imports_and_locals, sorted_statements, context, \
                           reserved_inputs=[], \
                           reserved_outputs=[], \
                           mode=mode)

    def export_as_script(self, filename, mode="w"):
        """ Export this experiment as a script for batch application. 
        
        It puts together the model (i.e. code) and the parameters 
        (i.e. context) to create a self-contained script to be used 
        in batch application. 
        """
        imports_and_locals = self.exec_model.imports_and_locals
        sorted_statements = self.exec_model.sorted_statements
        context = self.context

        # Clean context
        self.exec_model._clean_old_results_from_context(context.subcontext)

        export_as_script(filename, \
                         imports_and_locals, sorted_statements, context, \
                         reserved_inputs=[], \
                         reserved_outputs=[], \
                         mode=mode)

    #---------------------------------------------------------------------
    # Trait Event Handlers
    #---------------------------------------------------------------------

    @on_trait_change('exec_model')
    def _exec_model_changed(self, name, old, new):
        """ Propagate the change to the objects under this one.
        """
        if self.controller is not None:
            self.controller.execution_model = new
        if self.context is not None:
            self.context.executable = new

    #---------------------------------------------------------------------
    # Property getters/setters
    #---------------------------------------------------------------------

    def _get_shared_context(self):
        return self._shared_context

    def _set_shared_context(self, newcontext):
        subcontexts = self.context.subcontext.subcontexts
        if self._shared_context is not None:
            assert (self._shared_context in subcontexts)
            if newcontext is None:
                subcontexts.remove(self._shared_context)
                self._shared_context = None
            else:
                # Swap out the new context for the old, in-place
                ndx = subcontexts.index(self._shared_context)
                subcontexts[ndx] = newcontext
                self._shared_context = newcontext
        elif newcontext is not None:
            self._shared_context = newcontext
            subcontexts.append(newcontext)
        return

    def _update_exec_context(self):
        mc = MultiContext(
            # Put the function filter in front so we don't dirty up the data
            # context with function objects.
            FunctionFilterContext(name='functions'),
            self._local_context,
            name='multi',
        )

        if self._shared_context is not None:
            mc.subcontexts.append(self._shared_context)

        self.context = ExecutingContext(
            executable=self.exec_model,
            subcontext=mc,
            name='exec',
        )