Beispiel #1
0
def start_connect(world, sim_name, sim_config, sim_id, sim_params):
    """Connect to the already running simulator *sim_name* based on its config
    entry *sim_config*.

    Return a :class:`RemoteProcess` instance.

    Raise a :exc:`~mosaik.exceptions.ScenarioError` if the simulator cannot be
    instantiated.

    """
    addr = sim_config['connect']
    try:
        host, port = addr.strip().split(':')
        addr = (host, int(port))
    except ValueError:
        raise ScenarioError('Simulator "%s" could not be started: Could not '
                            'parse address "%s"' %
                            (sim_name, sim_config['connect'])) from None

    proxy = make_proxy(world,
                       sim_name,
                       sim_config,
                       sim_id,
                       sim_params,
                       addr=addr)
    return proxy
Beispiel #2
0
 def _assert_async_requests(self, dfg, src_sid, dest_sid):
     """Check if async. requests are allowed from *dest_sid* to *src_sid*
     and raise a :exc:`ScenarioError` if not."""
     data = {
         'src': src_sid,
         'dest': dest_sid,
     }
     if dest_sid not in dfg[src_sid]:
         raise ScenarioError(
             'No connection from "%(src)s" to "%(dest)s": You need to '
             'connect entities from both simulators and set '
             '"async_requests=True".' % data)
     if dfg[src_sid][dest_sid]['async_requests'] is not True:
         raise ScenarioError(
             'Async. requests not enabled for the connection from '
             '"%(src)s" to "%(dest)s". Add the argument '
             '"async_requests=True" to the connection of entities from '
             '"%(src)s" to "%(dest)s".' % data)
Beispiel #3
0
    def _check_model_and_meth_names(self, models, api_methods, extra_methods):
        """Check if there are any overlaps in model names and reserved API
        methods as well as in them and extra API methods.

        Raise a :exc:`~mosaik.exception.ScenarioError` if that's the case.

        """
        models = list(models)
        illegal_models = set(models) & set(api_methods)
        if illegal_models:
            raise ScenarioError('Simulator "%s" uses illegal model names: %s' %
                                (self.sid, ', '.join(illegal_models)))

        illegal_meths = set(models + api_methods) & set(extra_methods)
        if illegal_meths:
            raise ScenarioError('Simulator "%s" uses illegal extra method '
                                'names: %s' %
                                (self.sid, ', '.join(illegal_meths)))
Beispiel #4
0
def validate_api_version(version):
    """Validate the *version*.

    Raise a :exc: `ScenarioError` if the version format is wrong or
    does not match the min requirements.

    """
    try:
        version_tuple = str(version).split('.')
        v_tuple = tuple(map(int, version_tuple))
    except ValueError:
        raise ScenarioError('Version parts of %r must be integer' %
                            version) from None
    if len(v_tuple) != 2:
        raise ScenarioError('Version must be formated like '
                            '"major.minor", but is %r' % version) from None
    if not (v_tuple[0] == API_MAJOR and v_tuple[1] <= API_MINOR):
        raise ScenarioError('Version must be between %(major)s.0 and '
                            '%(major)s.%(minor)s' % {
                                'major': API_MAJOR,
                                'minor': API_MINOR
                            })

    return v_tuple
Beispiel #5
0
def start_dockerimage(world, sim_name, sim_config, sim_id, sim_params):
    """
    ...
    """
    print('###### start_dockerimage ######')
    replacements = {
        'addr': '%s:%s' % (world.config['addr'][0], world.config['addr'][1]),
        'python': sys.executable,
    }
    cmd = sim_config['dockerimage'] % replacements
    if 'posix' in sim_params.keys():
        posix = sim_params.pop('posix')
        cmd = shlex.split(cmd, posix=posix)
    else:
        cmd = shlex.split(cmd, posix=(os.name != 'nt'))
    cwd = sim_config['cwd'] if 'cwd' in sim_config else '.'

    # Make a copy of the current env. vars dictionary and update it with the
    # user provided values (or an empty dict as a default):
    env = dict(os.environ)
    env.update(sim_config.get('env', {}))

    kwargs = {
        'bufsize': 1,
        'cwd': cwd,
        'universal_newlines': True,
        'env': env,  # pass the new env dict to the sub process
    }
    try:
        proc = subprocess.Popen(cmd, **kwargs)
    except (FileNotFoundError, NotADirectoryError) as e:
        # This distinction has to be made due to a change in python 3.8.0.
        # It might become unecessary for future releases supporting
        # python >= 3.8 only.
        if str(e).count(':') == 2:
            eout = e.args[1]
        else:
            eout = str(e).split('] ')[1]
        raise ScenarioError('Simulator "%s" could not be started: %s' %
                            (sim_name, eout)) from None

    proxy = make_proxy(world,
                       sim_name,
                       sim_config,
                       sim_id,
                       sim_params,
                       proc=proc)
    return proxy
Beispiel #6
0
def start_proc(world, sim_name, sim_config, sim_id, sim_params):
    """Start a new process for simulator *sim_name* based on its config entry
    *sim_config*.

    Return a :class:`RemoteProcess` instance.

    Raise a :exc:`~mosaik.exceptions.ScenarioError` if the simulator cannot be
    instantiated.

    """
    replacements = {
        'addr': '%s:%s' % (world.config['addr'][0], world.config['addr'][1]),
        'python': sys.executable,
    }
    cmd = sim_config['cmd'] % replacements
    if 'posix' in sim_params.keys():
        posix = sim_params.pop('posix')
        cmd = shlex.split(cmd, posix=posix)
    else:
        cmd = shlex.split(cmd, posix=(os.name != 'nt'))
    cwd = sim_config['cwd'] if 'cwd' in sim_config else '.'

    # Make a copy of the current env. vars dictionary and update it with the
    # user provided values (or an empty dict as a default):
    env = dict(os.environ)
    env.update(sim_config.get('env', {}))

    kwargs = {
        'bufsize': 1,
        'cwd': cwd,
        'universal_newlines': True,
        'env': env,  # pass the new env dict to the sub process
    }
    try:
        proc = subprocess.Popen(cmd, **kwargs)
    except (FileNotFoundError, NotADirectoryError) as e:
        raise ScenarioError('Simulator "%s" could not be started: %s' %
                            (sim_name, e.args[1])) from None

    proxy = make_proxy(world,
                       sim_name,
                       sim_config,
                       sim_id,
                       sim_params,
                       proc=proc)
    return proxy
Beispiel #7
0
def start_dockerfile(world, sim_name, sim_config, sim_id, sim_params):
    """
    ...
    """
    print('###### start_dockerfile ######')
    addr = sim_config['connect']
    try:
        host, port = addr.strip().split(':')
        addr = (host, int(port))
    except ValueError:
        raise ScenarioError('Simulator "%s" could not be started: Could not '
                            'parse address "%s"' %
                            (sim_name, sim_config['connect'])) from None

    proxy = make_proxy(world,
                       sim_name,
                       sim_config,
                       sim_id,
                       sim_params,
                       addr=addr)
    return proxy
Beispiel #8
0
def start_inproc(world, sim_name, sim_config, sim_id, sim_params):
    """Import and instantiate the Python simulator *sim_name* based on its
    config entry *sim_config*.

    Return a :class:`LocalProcess` instance.

    Raise a :exc:`~mosaik.exceptions.ScenarioError` if the simulator cannot be
    instantiated.

    """
    try:
        mod_name, cls_name = sim_config['python'].split(':')
        mod = importlib.import_module(mod_name)
        cls = getattr(mod, cls_name)
    except (AttributeError, ImportError, KeyError, ValueError) as err:
        if sys.version_info.major <= 3 and sys.version_info.minor < 6:
            detail_msgs = {
                ValueError:
                'Malformed Python class name: Expected "module:Class"',
                ImportError: 'Could not import module: %s' % err.args[0],
                AttributeError: 'Class not found in module',
            }
        else:
            detail_msgs = {
                ValueError:
                'Malformed Python class name: Expected "module:Class"',
                ModuleNotFoundError:
                'Could not import module: %s' % err.args[0],
                AttributeError: 'Class not found in module',
            }
        details = detail_msgs[type(err)]
        raise ScenarioError('Simulator "%s" could not be started: %s' %
                            (sim_name, details)) from None
    sim = cls()
    meta = sim.init(sim_id, **sim_params)
    # "meta" is module global and thus shared between all "LocalProcess"
    # instances. This may leed to problems if a user modfies it, so make
    # a deep copy of it for each instance:
    meta = copy.deepcopy(meta)
    return LocalProcess(sim_name, sim_id, meta, sim, world)
Beispiel #9
0
def start(world, sim_name, sim_id, sim_params):
    """Start the simulator *sim_name* based on the configuration im
    *world.sim_config*, give it the ID *sim_id* and pass the parameters of the
    dict *sim_params* to it.

    The sim config is a dictionary with one entry for every simulator. The
    entry itself tells mosaik how to start the simulator::

        {
            'ExampleSimA': {
                'python': 'example_sim.mosaik:ExampleSim',
            },
            'ExampleSimB': {
                'cmd': 'example_sim %(addr)s',
                'cwd': '.',
            },
            'ExampleSimC': {
                'connect': 'host:port',
            },
        }

    *ExampleSimA* is a pure Python simulator. Mosaik will import the module
    ``example_sim.mosaik`` and instantiate the class ``ExampleSim`` to start
    the simulator.

    *ExampleSimB* would be started by executing the command *example_sim* and
    passing the network address of mosaik das command line argument. You can
    optionally specify a *current working directory*. It defaults to ``.``.

    *ExampleSimC* can not be started by mosaik, so mosaik tries to connect to
    it.

    The function returns a :class:`mosaik_api.Simulator` instance.

    It raises a :exc:`~mosaik.exceptions.SimulationError` if the simulator
    could not be started.

    Return a :class:`SimProxy` instance.

    """
    try:
        sim_config = world.sim_config[sim_name]
    except KeyError:
        raise ScenarioError('Simulator "%s" could not be started: Not found '
                            'in sim_config' % sim_name)

    # Try available starters in that order and raise an error if none of them
    # matches:
    starters = collections.OrderedDict(python=start_inproc,
                                       cmd=start_proc,
                                       connect=start_connect)
    for sim_type, start in starters.items():
        if sim_type in sim_config:
            proxy = start(world, sim_name, sim_config, sim_id, sim_params)

            try:
                proxy.meta['api_version'] = validate_api_version(
                    proxy.meta['api_version'])
                return proxy
            except ScenarioError as se:
                raise ScenarioError('Simulator "%s" could not be started:'
                                    ' Invalid version "%s": %s' %
                                    (sim_name, proxy.meta['api_version'], se))
    else:
        raise ScenarioError('Simulator "%s" could not be started: '
                            'Invalid configuration' % sim_name)
Beispiel #10
0
    def connect(self,
                src,
                dest,
                *attr_pairs,
                async_requests=False,
                time_shifted=False,
                initial_data=None):
        """Connect the *src* entity to *dest* entity.

        Establish a data-flow for each ``(src_attr, dest_attr)`` tuple in
        *attr_pairs*. If *src_attr* and *dest_attr* have the same name, you
        you can optionally only pass one of them as a single string.

        Raise a :exc:`~mosaik.exceptions.ScenarioError` if both entities share
        the same simulator instance, if at least one (src. or dest.) attribute
        in *attr_pairs* does not exist, or if the connection would introduce
        a cycle in the data-flow (e.g., A → B → C → A).

        If the *dest* simulator may make asynchronous requests to mosaik to
        query data from *src* (or set data to it), *async_requests* should be
        set to ``True`` so that the *src* simulator stays in sync with *dest*.

        An alternative to asynchronous requests are time-shifted connections.
        Their data flow is always resolved after normal connections so that
        cycles in the data-flow can be realized without introducing deadlocks.
        For such a connection *time_shifted* should be set to ``True`` and
        *initial_data* should contain a dict with input data for the first
        simulation step of the receiving simulator.

        An alternative to using async_requests to realize cyclic data-flow
        is given by the time_shifted kwarg. If set to ``True`` it marks the connection
        as cycle-closing (e.g. C → A). It must always be used with initial_data
        specifying a dict with the data sent to the dest simulator at the first
        step (e.g. *{‘src_attr’: value}*).

        """
        if src.sid == dest.sid:
            raise ScenarioError('Cannot connect entities sharing the same '
                                'simulator.')

        if async_requests and time_shifted:
            raise ScenarioError(
                'Async_requests and time_shifted connections '
                'are incongruous methods for handling of cyclic '
                'data-flow. Choose one!')

        # Expand single attributes "attr" to ("attr", "attr") tuples:
        attr_pairs = tuple((a, a) if type(a) is str else a for a in attr_pairs)

        missing_attrs = self._check_attributes(src, dest, attr_pairs)
        if missing_attrs:
            raise ScenarioError('At least one attribute does not exist: %s' %
                                ', '.join('%s.%s' % x for x in missing_attrs))

        # Time-shifted connections for closing data-flow cycles:
        if time_shifted:
            self.shifted_graph.add_edge(src.sid, dest.sid)

            dfs = self.shifted_graph[src.sid][dest.sid].setdefault(
                'dataflows', [])
            dfs.append((src.eid, dest.eid, attr_pairs))

            if type(initial_data) is not dict or initial_data == {}:
                raise ScenarioError(
                    'Time shifted connections have to be '
                    'set with default inputs for the first step.')
            # list for assertion of correct assignment:
            check_attrs = [a[0] for a in attr_pairs]
            # Set default values for first data exchange:
            for attr, val in initial_data.items():
                if attr not in check_attrs:
                    raise ScenarioError(
                        'Incorrect attr "%s" in "initial_data".' % attr)
                self._shifted_cache[0].setdefault(src.sid, {})
                self._shifted_cache[0][src.sid].setdefault(src.eid, {})
                self._shifted_cache[0][src.sid][src.eid][attr] = val
        # Standard connections:
        else:
            # Add edge and check for cycles and the data-flow graph.
            self.df_graph.add_edge(src.sid,
                                   dest.sid,
                                   async_requests=async_requests)
            if not networkx.is_directed_acyclic_graph(self.df_graph):
                self.df_graph.remove_edge(src.sid, dest.sid)
                raise ScenarioError('Connection from "%s" to "%s" introduces '
                                    'cyclic dependencies.' %
                                    (src.sid, dest.sid))

            dfs = self.df_graph[src.sid][dest.sid].setdefault('dataflows', [])
            dfs.append((src.eid, dest.eid, attr_pairs))

        # Add relation in entity_graph
        self.entity_graph.add_edge(src.full_id, dest.full_id)

        # Cache the attribute names which we need output data for after a
        # simulation step to reduce the number of df graph queries.
        outattr = [a[0] for a in attr_pairs]
        if outattr:
            self._df_outattr[src.sid][src.eid].extend(outattr)