BuildACell / txtlsim-python

Cell-free simulation toolbox in Python
BSD 3-Clause "New" or "Revised" License
4 stars 4 forks source link

How do we detect compilation loops? #22

Open WilliamIX opened 6 years ago

WilliamIX commented 6 years ago

When a component calls a mechanism, that could result in a new component being created, which in turn calls new mechanisms, which in turn could create more components, etc. This can, in theory, lead to compilation loops and infinite CRNs. We need to have some way to check for these loops and raise an error. This will be very helpful in debugging different mechanisms.

murrayrm commented 6 years ago

In the current code, mechanisms don't create components. They only create (intermediate) species and reactions. So it should be OK.

But something that could happen is that a component could call a subcomponent which could then (incorrectly) call a component in a way that created a cycle.

One easy fix is to just have the init constructor set a flag at the start that is reset at the end. Then check to make sure it isn't set when you enter the constructor. That might not work if things become multithreaded, but I don't see that as being an issue anytime soon.

WilliamIX commented 6 years ago

Okay, I think ensuring that components never create components is okay as a design constraint, which I will add in writing somewhere. However, I still think some kind of check is good if other people begin modifying expanding the code. I'm not sure I follow the flag idea though - could you elaborate?

murrayrm commented 6 years ago

Another place where this can come up is mechanisms calling mechanisms.

Rough code for a simple recursion check:

class MyComponent(Component):
    def __init__(self):
        if hasattr(self, 'initializing') and self.initializing:
            raise RuntimeError("unexpected recursion detected")
        self.initializing = True
        # Do the actual initialization
        self.initializing = False

If I didn't screw it up, this will raise an exception if an Component calls itself (possibly through other Components).

WilliamIX commented 6 years ago

I don't think this will work because if a new instance of a component is instantiated, it's initialized flag will be different from other instances. However, maybe the way to do something similar via the mixture class (which we can make call the entire compilation). Something like:

class Mixture(): def compile(self, debugging = False): compilation_dict = {} for component in self.get_components(): if component.name in compilation_dict and compilation_dict[component]: raise CompilationError("Component is being compiled multiple times") else: component.compile() compilation_dict[component.name] = True

And a similar checker can go inside components when they call mechanisms.

Do you think this will work?

murrayrm commented 6 years ago

Won't your code fail if you have multiple copies of a given component (eg, multiple DNA assemblies that use cds_tetr would have the cds_tetr component show up, but the call is not recursive)?

For example, I think the following could generate a CompilationError:

mix1 = Mixture('mymixture')
gene1 = txtl.assemble_dna(prom='ptet', utr5 = 'BCD2', cds = 'deGFP')
gene2 = txtl.assemble_dna(prom='pconst', utr5 = 'BCD2', cds = 'deGFP')
txtl.add_dna(mix1, [gene1, gene2], [1, 1], 'plasmid')
mix1.compile()

This code will instantiate the cds_degfp component twice, but it should be fine since it is just two different pieces of DNA that happen to use the same coding sequence.

(Note: the last function doesn't work in the current implementation, but write_sbml would do roughly the same).

WilliamIX commented 6 years ago

You're right - if our components are parts (promoters, utr5s, etc) my version will not work. If our components are assemblies (and they have distinct names), my code should work. I think what we are getting into is an issue where some components (such as DNA assemblies) are containing other components (parts). Perhaps the abstraction we need is for there to be components and subcomponents which are treated differently for some things?

WilliamIX commented 6 years ago

This high level design document I just put together includes loop checking. In order for this to work, we might need to make a new class "part" which is not a component, but is used by multi-part components. Otherwise the compilation is very similar to what we have been discussing. https://docs.google.com/document/d/1dsfYztoGJm44a956AtZJazuh4ixhefm29sDDWbmD6Yo/edit?usp=sharing