benbrastmckie / ModelChecker

A hyperintensional theorem prover for counterfactual conditional, modal, constitutive explanatory, relevance, and extensional operators.
https://pypi.org/project/model-checker/
7 stars 0 forks source link

operator container #39

Closed benbrastmckie closed 1 month ago

benbrastmckie commented 2 months ago

I was thinking about how to pass the collection of operators into ModelSetup. Here is one approach I was thinking about:

class OperatorCollection:
    def __init__(self):
        self.operators = {}

    def add_operator(self, operator):
        self.operators[operator.name] = operator

    def get_operator(self, name):
        return self.operators.get(name)

# Usage:
collection = OperatorCollection()
collection.add_operator(AndOperator(semantics))

Another reasonable option seems to be to use a dictionary, though the above looks like it might be a bit more flexible, allowing the user to add or potentially remove operators. I'm not totally sure what utilities will be convenient to the user, but figured this is worth considering. Since the operator won't change in looking for a model, we really just need a way for the user to specify which operators to include, so maybe a dictionary is all that is needed. Nevertheless, I'm trying to anticipate what might be required to compare two systems, and that down the line we might need greater flexibility. Curious to get your thoughts.

mbuit82 commented 2 months ago

For the user a simple list or thing they can treat like a list I think is best—here's an example of how a list would look (just thought I'd show because I implemented it before looking at this):

premises = ['\\neg (A \\vee B)', '(C \\wedge D)']
conclusions = ['(\\neg B \\wedge \\neg D)']
operators = [AndOperator, NegOperator, OrOperator]
semantics = Semantics(5)
model_setup = ModelSetup(premises, conclusions, semantics, 10000, False, False, False, operators)

If someone wanted to add or remove operators, they could simply change the list they pass into the ModelSetup object. However for reasons of back end implementation things (to find operators you look for a matching name and then make an instance of that operator with the semantics, which would be facilitated with a dictionary with names as keys and classes or instances as values), a dictionary would be better. Assuming we make a dictionary, it would be easier on the user to do an OperatorCollection approach because if the user creates a simple dictionary there's room for silly mistakes as to mismatching an operator and its name, and in any case, since the reason for having the dictionary is for some back end things, maybe having the raw dictionary for users to edit would just be confusing in the first place when a list (or list-like thing, like the OperatorCollection you have above) is more intuitive. I say OperatorCollection is list-like because users only add singular things, not pairs of things as in a dict—they'd effectively never know about the internal dictionary in there.

mbuit82 commented 2 months ago

I implemented the OperatorCollection class largely as you described. The code is at the top of hidden_things.py. Here is what it would look like in use:

premises = ['\\neg (A \\vee B)', '(C \\wedge D)']
conclusions = ['(\\neg B \\wedge \\neg D)']
operators = OperatorCollection(AndOperator, NegOperator, OrOperator)
semantics = Semantics(5)
model_setup = ModelSetup(premises, conclusions, semantics, 10000, False, False, False, operators)
solve_output = model_setup.solve()
model_structure = ModelStructure(*solve_output)

Other valid ways to make an OperatorCollection instance:

# 1: input as list (also works with tuples and sets)
operators = OperatorCollection([AndOperator, NegOperator, OrOperator])
# 2: create an instance and add them with the add_operator method
operators = OperatorCollection()
operators.add_operator(AndOperator) # add individually
operators.add_operator([NegOperator, OrOperator]) # or as a list (also works with tuples and sets)
# I don't think this one works: operators.add_operator(NegOperator, OrOperator) (ie multiple args)

in an instance of an OperatorCollection, there is a dictionary that collects as keys the names of the operators and as values the class that corresponds—not an instance of the class (ie, not actually an Operator object), but the class; the reason for this is that any Operator object needs a semantics to initalize, so by not having Operator instances in the values of the dictionary internal to an OperatorCollection object, we can use the same OperatorCollection object for two different semantics as opposed to having to make two different OperatorCollections with different semantics for each operator. Another reason is that if we had the values be Operator instances, a user could in theory have an OperatorCollection all of whose Operator instances reference a different semantics from the one passed in to the ModelSetup they both go into.

Then, when an OperatorCollection object is passed into a ModelSetup object, what gets saved in the ModelSetup object is a dictionary with keys of the names and values of instances of the operator classes, initialized with the semantics passed in to the ModelSetup object. The reason for the dictionary is what I mentioned above—there's a couple points where this dictionary setup is nicer than a list. This setup also avoids the problem mentioned in the last sentence of the previous paragraph.

Please let me know if you have any questions about the implementation or run in to any bugs! I tested the code with a few basic cases but maybe something more complicated will throw it in a loop (not sure how much more complicated it could get though).

benbrastmckie commented 2 months ago

That sounds great! I'm excited to dig into the details after my deadline tomorrow. Seems like it is coming together nicely.

benbrastmckie commented 1 month ago

This seems to have been largely resolved. OperatorContainer looks great!