quaquel / pyNetLogo

BSD 3-Clause "New" or "Revised" License
82 stars 22 forks source link

High-level API #5

Closed Vayel closed 3 years ago

Vayel commented 6 years ago

It would be useful to have an API dealing with inputs and outputs rather than with raw commands. Something like this:

class Model:
    def __init__(self, path, inputs, outputs, **kwargs):
        self._link = pyNetLogo.NetLogoLink(**kwargs)
        self._link.load_model(path)
        self.inputs = inputs
        self.outputs = outputs

    def configure(self, inputs):
        for name, input in self.inputs.items():
            val = inputs.get(name, input['default'])
            low, high = input['bounds']
            assert low <= val <= high
            self._link.command('set {0} {1}'.format(name, val))

    def setup(self, inputs=None):
        if inputs is not None:
            self.configure(inputs)
        self._link.command('setup')
        return {
            o: self._link.report(o)
            for o in self.outputs
        }

    def step(self, steps, inputs=None, go='go'):
        if inputs is not None:
            self.configure(inputs)
        return self._link.report_many(self.outputs, steps, go=go)

    def run(self, steps, inputs=None):
        setup_data = self.setup(inputs=inputs)
        step_data = self.step(steps)
        return {
            o: [setup_data[o], *step_data[o]]
            for o in self.outputs
        }

Which would be used like this:

inputs = {
    'initial-number-sheep': {
        'bounds': [0, 250],
        'default': 50
    },
    # ...
}
outputs = ['count sheep']
model = Model('abm.nlogo', inputs, outputs, netlogo_home='...', netlogo_version='6')
data = model.run(100, {'initial-number-sheep': 73})
quaquel commented 6 years ago

I have to think about this. My design so far has been to maintain parity with RNetLogo as much as possible. I can see the value of a simple high level interface for simple cases.

Vayel commented 6 years ago

Actually, this is quite similar to BaseNetLogoModel.

quaquel commented 6 years ago

The issue with any high level API is that it can be too limited, or have weird unexpected corner cases, or requires detailed explanations of the limitations.

For example BaseNetLogoModel requires that you can specify the entire go routine from within python. Any loading of files etc. should be handled outside the go routine. Also, it relies on using text files to send results from netlogo to python. This creates IO overhead which can be slow.

I am happy to consider including something like BaseNetLogo model within pynetlogo, but it would require some careful design to cope with these various issues.