Exemple #1
0
    def test_iterate_over_interventions(self, get_intervention):
        """Test __iter___ method of AssetRegister class

        """
        asset_one = get_intervention
        register = InterventionRegister()
        register.register(asset_one)

        for asset in register:
            assert asset.sha1sum() == asset_one.sha1sum()
Exemple #2
0
    def test_add_duplicate_intervention(self, get_intervention):
        """Tests that only unique interventions are retained

        """
        asset_one = get_intervention
        asset_two = get_intervention
        register = InterventionRegister()
        register.register(asset_one)
        register.register(asset_two)

        assert len(register) == 1
Exemple #3
0
    def test_retrieve_intervention_by_name(self, get_intervention):
        water_treatment_plant = get_intervention
        register = InterventionRegister()
        register.register(water_treatment_plant)

        # pick an asset from the list - this is what the optimiser will do
        asset = register.get_intervention('water_treatment_plant')
        assert asset.name == "water_treatment_plant"

        assert asset.data == {
            'name': 'water_treatment_plant',
            'sector': 'water_supply',
            'capacity': {
                'units': 'ML/day',
                'value': 5
            },
            'location': 'oxford'
        }
Exemple #4
0
    def __init__(self, name):
        # housekeeping
        super().__init__(name)
        self.logger = logging.getLogger(__name__)
        self.max_iterations = 25
        self.convergence_relative_tolerance = 1e-05
        self.convergence_absolute_tolerance = 1e-08

        # models - includes types of SectorModel and ScenarioModel
        self.dependency_graph = networkx.DiGraph()

        # systems, interventions and (system) state
        self.timesteps = []
        self.interventions = InterventionRegister()
        self.initial_conditions = []
        self.planning = Planning([])
        self._state = defaultdict(dict)

        # scenario data and results
        self._results = defaultdict(dict)
Exemple #5
0
def build_register_two(build_intervention_es, build_intervention_ws):
    water = Intervention(data=build_intervention_ws)
    energy = Intervention(data=build_intervention_es)

    register = InterventionRegister()
    register.register(energy)
    register.register(water)
    return register
Exemple #6
0
 def test_error_when_retrieve_by_name(self, get_intervention):
     water_treatment_plant = get_intervention
     register = InterventionRegister()
     register.register(water_treatment_plant)
     with raises(ValueError) as excinfo:
         register.get_intervention('not_here')
     expected = "Intervention 'not_here' not found in register"
     assert str(excinfo.value) == expected
Exemple #7
0
    def test_register_intervention(self, get_intervention):
        water_treatment_plant = get_intervention
        register = InterventionRegister()
        register.register(water_treatment_plant)

        assert len(register) == 1
        assert sorted(register._attribute_keys) == [
            "capacity", "location", "name", "sector"
        ]

        attr_idx = register.attribute_index("name")
        possible = register._attribute_possible_values[attr_idx]
        assert possible == [None, "water_treatment_plant"]

        attr_idx = register.attribute_index("capacity")
        possible = register._attribute_possible_values[attr_idx]
        assert possible == [None, {'units': 'ML/day', 'value': 5}]
Exemple #8
0
 def test_register_len_one(self, build_intervention_ws):
     water = Intervention(data=build_intervention_ws)
     register = InterventionRegister()
     register.register(water)
     assert len(register) == 1
Exemple #9
0
class SosModel(CompositeModel):
    """Consists of the collection of models joined via dependencies

    This class is populated at runtime by the :class:`SosModelBuilder` and
    called from :func:`smif.cli.run_model`.  SosModel inherits from
    :class:`smif.composite.Model`.

    Arguments
    ---------
    name : str
        The unique name of the SosModel

    """
    def __init__(self, name):
        # housekeeping
        super().__init__(name)
        self.logger = logging.getLogger(__name__)
        self.max_iterations = 25
        self.convergence_relative_tolerance = 1e-05
        self.convergence_absolute_tolerance = 1e-08

        # models - includes types of SectorModel and ScenarioModel
        self.dependency_graph = networkx.DiGraph()

        # systems, interventions and (system) state
        self.timesteps = []
        self.interventions = InterventionRegister()
        self.initial_conditions = []
        self.planning = Planning([])
        self._state = defaultdict(dict)

        # scenario data and results
        self._results = defaultdict(dict)

    def add_model(self, model):
        """Adds a sector model to the system-of-systems model

        Arguments
        ---------
        model : :class:`smif.sector_model.SectorModel`
            A sector model wrapper

        """
        assert isinstance(model, Model)
        self.logger.info("Loading model: %s", model.name)
        self.models[model.name] = model

    @property
    def results(self):
        """Get nested dict of model results

        Returns
        -------
        dict
            Nested dictionary in the format
            results[str:model][str:parameter]
        """
        # convert from defaultdict to plain dict
        return dict(self._results)

    def simulate(self, timestep, data=None):
        """Run the SosModel

        Returns
        -------
        results : dict
            Nested dict keyed by model name, parameter name

        """
        self.check_dependencies()
        run_order = self._get_model_sets_in_run_order()
        self.logger.info("Determined run order as %s",
                         [x.name for x in run_order])
        results = {}
        for model in run_order:
            # get data for model
            # TODO settle and test data dict structure/object between simple/composite models
            sim_data = {}
            for input_name, dep in model.deps.items():
                input_ = model.model_inputs[input_name]
                if input_ in self.free_inputs:
                    # pick external dependencies from data
                    param_data = data[dep.source_model.name][dep.source.name]
                else:
                    # pick internal dependencies from results
                    param_data = results[dep.source_model.name][
                        dep.source.name]
                param_data_converted = dep.convert(param_data, input_)
                sim_data[input_.name] = param_data_converted

            sim_data = self._get_parameter_values(model, sim_data, data)

            sim_results = model.simulate(timestep, sim_data)
            for model_name, model_results in sim_results.items():
                results[model_name] = model_results
        return results

    def check_dependencies(self):
        """For each contained model, compare dependency list against
        list of available models and build the dependency graph
        """
        if self.free_inputs.names:
            msg = "A SosModel must have all inputs linked to dependencies." \
                  "Define dependencies for %s"
            raise NotImplementedError(msg, ", ".join(self.free_inputs.names))

        for model in self.models.values():

            if isinstance(model, SosModel):
                msg = "Nesting of SosModels not yet supported"
                raise NotImplementedError(msg)
            else:
                self.dependency_graph.add_node(model, name=model.name)

                for sink, dependency in model.deps.items():
                    provider = dependency.source_model
                    msg = "Dependency '%s' provided by '%s'"
                    self.logger.debug(msg, sink, provider.name)

                    self.dependency_graph.add_edge(provider, model, {
                        'source': dependency.source,
                        'sink': sink
                    })

    def get_decisions(self, model, timestep):
        """Gets the interventions that correspond to the decisions

        Parameters
        ----------
        model: :class:`smif.sector_model.SectorModel`
            The instance of the sector model wrapper to run
        timestep: int
            The current model year

        TODO: Move into DecisionManager class
        """
        self.logger.debug("Finding decisions for %i", timestep)
        current_decisions = []
        for decision in self.planning.planned_interventions:
            if decision['build_date'] <= timestep:
                name = decision['name']
                if name in model.intervention_names:
                    msg = "Adding decision '%s' to instruction list"
                    self.logger.debug(msg, name)
                    intervention = self.interventions.get_intervention(name)
                    current_decisions.append(intervention)
        # for decision in self.planning.get_rule_based_interventions(timestep):
        #   current_decisions.append(intervention)
        # for decision in self.planning.get_optimised_interventions(timestep):
        #   current_decisions.append(intervention)

        return current_decisions

    def get_state(self, model, timestep):
        """Gets the state to pass to SectorModel.simulate
        """
        if model.name not in self._state[timestep]:
            self.logger.warning("Found no state for %s in timestep %s",
                                model.name, timestep)
            return []
        return self._state[timestep][model.name]

    def set_state(self, model, from_timestep, state):
        """Sets state output from model ready for next timestep
        """
        for_timestep = self.timestep_after(from_timestep)
        self._state[for_timestep][model.name] = state

    def set_data(self, model, timestep, results):
        """Sets results output from model as data available to other/future models

        Stores only latest estimated results (i.e. not holding on to iterations
        here while trying to solve interdependencies)
        """
        self._results[timestep][model.name] = results

    def _get_model_sets_in_run_order(self):
        """Returns a list of :class:`Model` in a runnable order.

        If a set contains more than one model, there is an interdependency and
        and we attempt to run the models to convergence.

        Returns
        -------
        list
            A list of `smif.model.Model` objects
        """
        if networkx.is_directed_acyclic_graph(self.dependency_graph):
            # topological sort gives a single list from directed graph, currently
            # ignoring opportunities to run independent models in parallel
            run_order = networkx.topological_sort(self.dependency_graph,
                                                  reverse=False)

            # list of Models (typically ScenarioModel and SectorModel)
            ordered_sets = list(run_order)

        else:
            # contract the strongly connected components (subgraphs which
            # contain cycles) into single nodes, producing the 'condensation'
            # of the graph, where each node maps to one or more sector models
            condensation = networkx.condensation(self.dependency_graph)

            # topological sort of the condensation gives an ordering of the
            # contracted nodes, whose 'members' attribute refers back to the
            # original dependency graph
            ordered_sets = []
            for node_id in networkx.topological_sort(condensation,
                                                     reverse=False):
                models = condensation.node[node_id]['members']
                if len(models) == 1:
                    ordered_sets.append(models.pop())
                else:
                    ordered_sets.append(
                        ModelSet(models,
                                 max_iterations=self.max_iterations,
                                 relative_tolerance=self.
                                 convergence_relative_tolerance,
                                 absolute_tolerance=self.
                                 convergence_absolute_tolerance))

        return ordered_sets

    def determine_running_mode(self):
        """Determines from the config in what mode to run the model

        Returns
        =======
        :class:`RunMode`
            The mode in which to run the model
        """

        number_of_timesteps = len(self.timesteps)

        if number_of_timesteps > 1:
            # Run a sequential simulation
            mode = RunMode.sequential_simulation

        elif number_of_timesteps == 0:
            raise ValueError("No timesteps have been specified")

        else:
            # Run a single simulation
            mode = RunMode.static_simulation

        return mode

    def timestep_before(self, timestep):
        """Returns the timestep previous to a given timestep, or None

        Arguments
        ---------
        timestep : str

        Returns
        -------
        str

        """
        return element_before(timestep, self.timesteps)

    def timestep_after(self, timestep):
        """Returns the timestep after a given timestep, or None

        Arguments
        ---------
        timestep : str

        Returns
        -------
        str
        """
        return element_after(timestep, self.timesteps)

    @property
    def intervention_names(self):
        """Names (id-like keys) of all known asset type
        """
        return [intervention.name for intervention in self.interventions]

    @property
    def sector_models(self):
        """The list of sector model names

        Returns
        =======
        list
            A list of sector model names
        """
        return [
            x for x, y in self.models.items() if isinstance(y, SectorModel)
        ]

    @property
    def scenario_models(self):
        """The list of scenario model names

        Returns
        -------
        list
            A list of scenario model names
        """
        return [
            x for x, y in self.models.items() if isinstance(y, ScenarioModel)
        ]