Example #1
0
class Component(Base):
    type =
    frag_func = Element('DamageAlgorithm', 'Fragility algorithm', Element.NO_DEFAULT)
    recovery_func = Element('DamageAlgorithm', 'Recovery algorithm', Element.NO_DEFAULT)

    def expose_to(self, intensity_param):
        return self.frag_func(intensity_param)
Example #2
0
class ConnectionValues(Base):
    """
    Each connection between two components has a capacity and
    a weight.
    """
    link_capacity = Element('float', 'Link capacity', 0.0)
    weight = Element('float', 'Weight', 0.0)
Example #3
0
class RecoveryState(Base):
    recovery_mean = Element('float', 'Recovery mean', 0.0,
                            [lambda x: float(x) > 0.0])
    recovery_std = Element('float', 'Recovery standard deviation', 0.0,
                           [lambda x: float(x) > 0.0])
    recovery_95percentile = Element('float', 'Recovery 95th percentile', 0.0,
                                    [lambda x: float(x) > 0.0])
Example #4
0
class LogNormalCDF(ResponseModel):
    median = Element('float', 'Median of the log normal CDF.',
                     Element.NO_DEFAULT, [lambda x: float(x) > 0.])
    beta = Element('float', 'Log standard deviation of the log normal CDF',
                   Element.NO_DEFAULT, [lambda x: float(x) > 0.])

    def __call__(self, value):
        import scipy.stats as stats
        return stats.lognorm.cdf(value, self.beta, scale=self.median)
Example #5
0
class Model(Base):
    description = Info('Represents a model (e.g. a "model of a powerstation")')

    components = Element('dict', 'A component', dict,
        [lambda x: [isinstance(y, Component) for y in itervalues(x)]])

    name = Element('str', "The model's name", 'model')

    def add_component(self, name, component):
        self.components[name] = component
Example #6
0
class DamageAlgorithm(Base):
    damage_states = Element(
        'dict', 'Response models for the damage stages',
        [lambda x: [isinstance(y, DamageState) for y in x.itervalues()]])

    def __call__(self, intensity_param, state):
        return self.damage_states[state](intensity_param)
Example #7
0
class StepFunc(ResponseModel):
    xys = Element('XYPairs', 'A list of X, Y pairs.', list,
                  [lambda xy: [(float(x), float(y)) for x, y in xy]])

    dummy = Element('Unreachable_from_elements', 'Unreachable from elements',
                    Unreachable_from_elements)

    def __call__(self, value):
        """
        Note that intervals are closed on the right.
        """

        for x, y in self.xys:
            if value < x:
                return y

        raise ValueError('value is greater than all xs!')
Example #8
0
class RecoveryAlgorithm(Base):
    recovery_states = Element(
        'IODict', 'Recovery models for the damage states',
        [lambda x: [isinstance(y, RecoveryState) for y in x.itervalues()]])

    def __call__(self, intensity_param, state):
        for recovery_state in self.recovery_states:
            recovery_state(intensity_param)

        return
Example #9
0
    def test_cannot_have_fields(self):
        """
        Check that we cannot create a model containing elements with
        dissallowed names, such as "predecessor".
        """

        with self.assertRaises(ValueError):
            cls = type(
                'Tst', (Base, ),
                {'predecessor': Element('object', 'dissallowed name', object)})
Example #10
0
class Component(Base):
    """Fundamental unit of an infrastructure system. It
    contains the categorical definitions of the component
    as well as the algorithms that assess the damage and recovery."""

    component_type = Element('str', 'Type of component.')
    component_class = Element('str', 'Class of component.')
    node_type = Element('str', 'Class of node.')
    node_cluster = Element('str', 'Node cluster.')
    operating_capacity = Element('str', 'Node cluster.')

    frag_func = Element('DamageAlgorithm', 'Fragility algorithm',
                        Element.NO_DEFAULT)
    recovery_func = Element('RecoveryAlgorithm', 'Recovery algorithm',
                            Element.NO_DEFAULT)

    destination_components = Element('IODict', 'List of connected components',
                                     IODict)

    def expose_to(self, hazard_level, scenario):
        """
        Expose the component to the hazard using the
        damage algorithm.
        :param hazard_level: the hazard level
        :param scenario: Additional parameters that may be required to assess hazard damage.
        :return: The array of probabilities that each damage level was exceeded.
        """
        component_pe_ds = self.frag_func.pe_ds(hazard_level)

        return component_pe_ds

    def get_damage_state(self, ds_index):
        """
        Return the required damage state.
        :param ds_index: The index of the damage state, as supplied by expose_to method.
        :return: The fragility function object
        """
        return self.frag_func.damage_states.index(ds_index)

    def get_recovery(self, ds_index):
        """
        The fragility function and recovery functions are both indexed on the same damage
        state index
        :param ds_index: Index of the damage state.
        :return: recovery function object
        """
        return self.recovery_func.recovery_states.index(ds_index)
Example #11
0
class DamageAlgorithm(Base):
    damage_states = Element(
        'IODict', 'Response models for the damage states',
        [lambda x: [isinstance(y, DamageState) for y in x.itervalues()]])

    def pe_ds(self, intensity_param):
        pe_ds = np.zeros(len(self.damage_states))

        for offset, damage_state in enumerate(self.damage_states.itervalues()):
            if damage_state.mode != 1:
                raise RuntimeError("Mode {} not implemented".format(
                    damage_state.mode))
            pe_ds[offset] = damage_state(intensity_param)

        return pe_ds[1:]
Example #12
0
class DamageState(ResponseModel):
    damage_state = Element('str', 'Damage state name')
    damage_state_description = Element(
        'str', 'A description of what the damage state means')
    mode = Element('int', 'The mode used to estimate the damage')
    functionality = Element('float',
                            'Level of functionality for this damage level',
                            0.0, [lambda x: float(x) >= 0.0])
    fragility_source = Element('str', 'The source of the parameter values')
    damage_ratio = Element('float', 'damage ratio', 0.0,
                           [lambda x: float(x) >= 0.0])
Example #13
0
class IFSystem(Model):
    """
    The top level representation of a system that can respond to a
    range of hazards. It encapsulates a number of components that are
    used to estimate the response to various hazard levels.
    """

    name = Element('str', "The model's name", 'model')
    description = Info('Represents a model (e.g. a "model of a powerstation")')

    components = Element(
        'IODict', 'The components that make up the infrastructure system',
        IODict, [lambda x: [isinstance(y, Component) for y in x.itervalues()]])

    supply_nodes = Element(
        'dict', 'The components that supply the infrastructure system', {})
    output_nodes = Element(
        'dict', 'The components that output from the infrastructure system',
        {})

    supply_total = None
    component_graph = None
    if_nominal_output = None
    system_class = None

    sys_dmg_states = [
        'DS0 None', 'DS1 Slight', 'DS2 Moderate', 'DS3 Extensive',
        'DS4 Complete'
    ]

    def __init__(self, **kwargs):
        """
        Construct the infrastructure object
        :param kwargs: Objects making up the infrastructure
        """
        super(IFSystem, self).__init__(**kwargs)
        # TODO This assignment should be replaced by subclasses of IFSystem
        # instantiated directly by the model creator
        # This intitiates some instance members with the correct configuration.
        # Not sure why these aren't in the configuration file
        if self.system_class.lower() == 'substation':
            # Initiate the substation, note: this may not have been tested in this
            # version of the code.
            self.uncosted_classes = [
                'JUNCTION POINT', 'SYSTEM INPUT', 'SYSTEM OUTPUT', 'Generator',
                'Bus', 'Lightning Arrester'
            ]
            self.ds_lims_compclasses = {
                'Disconnect Switch': [0.05, 0.40, 0.70, 0.99, 1.00],
                'Circuit Breaker': [0.05, 0.40, 0.70, 0.99, 1.00],
                'Current Transformer': [0.05, 0.40, 0.70, 0.99, 1.00],
                'Voltage Transformer': [0.05, 0.40, 0.70, 0.99, 1.00],
                'Power Transformer': [0.05, 0.40, 0.70, 0.99, 1.00],
                'Control Building': [0.06, 0.30, 0.75, 0.99, 1.00]
            }
        elif self.system_class.lower() == 'powerstation':
            # Initiate the power station values, which have been used in all current
            # testing
            self.uncosted_classes = [
                'JUNCTION POINT', 'SYSTEM INPUT', 'SYSTEM OUTPUT'
            ]
            self.ds_lims_compclasses = {
                'Boiler': [0.0, 0.05, 0.40, 0.70, 1.00],
                'Control Building': [0.0, 0.05, 0.40, 0.70, 1.00],
                'Emission Management': [0.0, 0.05, 0.40, 0.70, 1.00],
                'Fuel Delivery and Storage': [0.0, 0.05, 0.40, 0.70, 1.00],
                'Fuel Movement': [0.0, 0.05, 0.40, 0.70, 1.00],
                'Generator': [0.0, 0.05, 0.40, 0.70, 1.00],
                'SYSTEM OUTPUT': [0.0, 0.05, 0.40, 0.70, 1.00],
                'Stepup Transformer': [0.0, 0.05, 0.40, 0.70, 1.00],
                'Turbine': [0.0, 0.05, 0.40, 0.70, 1.00],
                'Water System': [0.0, 0.05, 0.40, 0.70, 1.00]
            }

        component_graph = ComponentGraph(self.components)

    def add_component(self, name, component):
        """Add a component to the component dict"""
        self.components[name] = component

    def expose_to(self, hazard_level, scenario):
        """
        Exposes the components of the infrastructure to a hazard level
        within a scenario.
        :param hazard_level: The hazard level that the infrastructure is to be exposed to.
        :param scenario: The parameters for the scenario being simulated.
        :return: The state of the infrastructure after the exposure.
        """

        code_start_time = time.time(
        )  # keep track of the length of time the exposure takes

        # calculate the damage state probabilities
        component_damage_state_ind = self.probable_ds_hazard_level(
            hazard_level, scenario)

        # calculate the component loss, functionality, output,
        #  economic loss and recovery output over time
        component_sample_loss, \
        comp_sample_func, \
        if_sample_output, \
        if_sample_economic_loss, \
        if_output_given_recovery = self.calc_output_loss(scenario, component_damage_state_ind)

        # Construct the dictionary containing the statisitics of the response
        component_response = self.calc_response(component_sample_loss,
                                                comp_sample_func,
                                                component_damage_state_ind)

        # determine average output for the output components
        if_output = {}
        for output_index, (output_comp_id, output_comp) in enumerate(
                self.output_nodes.iteritems()):
            if_output[output_comp_id] = np.mean(if_sample_output[:,
                                                                 output_index])

        # log the elapsed time for this hazard level
        elapsed = timedelta(seconds=(time.time() - code_start_time))
        logging.info("[ Hazard {} run time: {} ]\n".format(
            hazard_level.hazard_intensity, str(elapsed)))

        # We combine the result data into a dictionary for ease of use
        response_dict = {
            hazard_level.hazard_intensity: [
                component_damage_state_ind, if_output, component_response,
                if_sample_output, if_sample_economic_loss,
                if_output_given_recovery
            ]
        }

        return response_dict

    def probable_ds_hazard_level(self, hazard_level, scenario):
        """
        Calculate the probability that being exposed to a hazard level
        will exceed the given damage levels for each component. A monte
        carlo approach is taken by simulating the exposure for the number
        of samples given in the scenario.
        :param hazard_level: Level of the hazard
        :param scenario: Parameters for the scenario
        :return: An array of the probability that each of the damage states were exceeded.
        """
        if scenario.run_context:
            # Use seeding for this test run for reproducibility, the seeding
            # is generated by converting the hazard intensity to an integer
            # after shifting by two decimal places.
            prng = np.random.RandomState(
                int(hazard_level.hazard_intensity * 100))
        else:
            # seeding was not used
            prng = np.random.RandomState()

        # record the number of elements for use
        num_elements = len(self.components)

        # construct a zeroed numpy array that can contain the number of samples for
        # each element.
        component_damage_state_ind = np.zeros(
            (scenario.num_samples, num_elements), dtype=int)
        # create another numpy array of random uniform [0,1.0) numbers.
        rnd = prng.uniform(size=(scenario.num_samples, num_elements))
        logging.debug("Hazard Intensity {}".format(
            hazard_level.hazard_intensity))
        # iterate through the components
        for index, comp_key in enumerate(sorted(self.components.keys())):
            component = self.components[comp_key]
            # use the components expose_to method to retrieve the probabilities
            # of this hazard level exceeding each of the components damage levels
            component_pe_ds = np.sort(
                component.expose_to(hazard_level, scenario))
            logging.debug("Component {} : pe_ds {}".format(
                component.component_id, component_pe_ds))

            # This little piece of numpy magic calculates the damage level by summing
            # how many damage states were exceeded.
            # Unpacking the calculation:
            # component_pe_ds is usually something like [0.01, 0.12, 0.21, 0.33]
            # rnd[:, index] gives the random numbers created for this component
            # with the first axis (denoted by :) containing the samples for this
            # hazard intensity. The [:, np.newaxis] broadcasts (https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html)
            # each randomn number across the supplied component_pe_ds. If the sample number
            # is greater than the first two numbers in component_pe_ds, the comparison > will
            # return [True, True, False, False]
            # the np.sum will convert this to [1, 1, 0, 0] and return 2. This is the resulting
            # damage level. This will complete the comparison for all of the samples
            # for this component
            component_damage_state_ind[:, index] = \
                np.sum(component_pe_ds > rnd[:, index][:, np.newaxis], axis=1)

        return component_damage_state_ind

    def calc_output_loss(self, scenario, component_damage_state_ind):
        """
        Calculate the results to the infrastructure given the damage state
        parameter
        :param scenario: Details of the scenario being run
        :param component_damage_state_ind: The array of the component's damage state samples
        :return: 5 lists of calculations
        """
        # Component loss caused by the damage
        if_level_loss = np.zeros((scenario.num_samples, len(self.components)),
                                 dtype=np.float64)
        # Infrastructure loss: sum of component loss
        if_level_economic_loss = np.zeros(scenario.num_samples,
                                          dtype=np.float64)
        # Component functionality
        if_level_functionality = np.zeros(
            (scenario.num_samples, len(self.components)), dtype=np.float64)
        # output for the level of damage
        if_level_output = np.zeros(
            (scenario.num_samples, len(self.output_nodes)), dtype=np.float64)
        # output available as recovery progresses
        if_output_given_recovery = np.zeros(
            (scenario.num_samples, scenario.num_time_steps), dtype=np.float64)

        # iterate through the samples
        for sample_index in range(scenario.num_samples):
            # initialise the function and loss arrays for the sample
            component_function_at_time = []
            comp_sample_loss = np.zeros(len(self.components))
            comp_sample_func = np.zeros(len(self.components))
            # Extract the array of damage states for this sample
            component_ds = component_damage_state_ind[sample_index, :]
            # iterate through the components
            for component_index, comp_key in enumerate(
                    sorted(self.components.keys())):
                component = self.components[comp_key]
                # get the damage state for the component
                damage_state = component.get_damage_state(
                    component_ds[component_index])
                # use the damage state attributes to calculate the loss and functionality for the component sample
                loss = damage_state.damage_ratio * component.cost_fraction
                comp_sample_loss[component_index] = loss
                comp_sample_func[component_index] = damage_state.functionality
                # calculate the recovery time
                component_function_at_time.append(
                    self.calc_recov_time_given_comp_ds(
                        component, component_ds[component_index], scenario))
            # save this sample's component loss and functionality
            if_level_loss[sample_index, :] = comp_sample_loss
            if_level_functionality[sample_index, :] = comp_sample_func
            # the infrastructure's economic loss for this sample is the sum of all
            # component losses
            if_level_economic_loss[sample_index] = np.sum(comp_sample_loss)
            # estimate the output for this sample's component functionality
            if_level_output[sample_index, :] = self.compute_output_given_ds(
                comp_sample_func)

            # calculate the restoration output
            component_function_at_time = np.array(component_function_at_time)
            for time_step in range(scenario.num_time_steps):
                if_output_given_recovery[sample_index, time_step] = \
                    sum(self.compute_output_given_ds(component_function_at_time[:, time_step]))

        return if_level_loss, \
               if_level_functionality, \
               if_level_output, \
               if_level_economic_loss, \
               if_output_given_recovery

    def get_nominal_output(self):
        """
        Estimate the output of the undamaged infrastructure output
        nodes.
        :return: Numeric value of aggregated output.
        """
        if not self.if_nominal_output:
            self.if_nominal_output = 0
            for output_comp_id, output_comp in self.output_nodes.iteritems():
                self.if_nominal_output += output_comp['output_node_capacity']

        return self.if_nominal_output

    def compute_output_given_ds(self, comp_level_func):
        """
        Using the graph of components, the output is calculated
        from the component functionality parameter.
        :param comp_level_func: An array that indicates the functionality level for each component.
        :return: An array of the output level for each output node.
        """
        # Create the component graph if one does not yet exist
        if not self.component_graph:
            self.component_graph = ComponentGraph(self.components,
                                                  comp_level_func)
        else:
            self.component_graph.update_capacity(self.components,
                                                 comp_level_func)

        # calculate the capacity
        system_flows_sample = []
        system_outflows_sample = np.zeros(len(self.output_nodes))
        for output_index, (output_comp_id, output_comp) in enumerate(
                self.output_nodes.iteritems()):
            # track the outputs by source type (e.g. water or coal)
            total_supply_flow_by_source = {}
            for supply_index, (supply_comp_id, supply_comp) in enumerate(
                    self.supply_nodes.iteritems()):
                if_flow_fraction = self.component_graph.maxflow(
                    supply_comp_id, output_comp_id)
                if_sample_flow = if_flow_fraction * supply_comp[
                    'capacity_fraction']

                if supply_comp[
                        'commodity_type'] not in total_supply_flow_by_source:
                    total_supply_flow_by_source[
                        supply_comp['commodity_type']] = if_sample_flow
                else:
                    total_supply_flow_by_source[
                        supply_comp['commodity_type']] += if_sample_flow

            total_available_flow = min(
                total_supply_flow_by_source.itervalues())

            estimated_capacity_fraction = min(total_available_flow,
                                              output_comp['capacity_fraction'])
            system_outflows_sample[
                output_index] = estimated_capacity_fraction * self.get_nominal_output(
                )

        return system_outflows_sample

    def calc_recov_time_given_comp_ds(self, component, damage_state, scenario):
        '''
        Calculates the recovery time of a component, given damage state index
        '''
        import scipy.stats as stats
        recovery_parameters = component.get_recovery(damage_state)
        damage_parameters = component.get_damage_state(damage_state)

        m = recovery_parameters.recovery_mean
        s = recovery_parameters.recovery_std
        fn = damage_parameters.functionality
        cdf = stats.norm.cdf(scenario.restoration_time_range, loc=m, scale=s)
        return cdf + (1.0 - cdf) * fn

    def calc_response(self, component_loss, comp_sample_func,
                      component_damage_state_ind):
        """
        Convert the arrays into dicts for subsequent analysis
        :param component_loss: The array of component loss values
        :param comp_sample_func: The array of component functionality values
        :param component_damage_state_ind: The array of component damage state indicators
        :return: A dict of component response statistics
        """
        comp_resp_dict = dict()

        for comp_index, comp_id in enumerate(np.sort(self.components.keys())):
            component = self.components[comp_id]
            comp_resp_dict[(comp_id, 'loss_mean')] \
                = np.mean(component_loss[:, comp_index])

            comp_resp_dict[(comp_id, 'loss_std')] \
                = np.std(component_loss[:, comp_index])

            comp_resp_dict[(comp_id, 'func_mean')] \
                = np.mean(comp_sample_func[:, comp_index])

            comp_resp_dict[(comp_id, 'func_std')] \
                = np.std(comp_sample_func[:, comp_index])

            comp_resp_dict[(comp_id, 'num_failures')] \
                = np.mean(component_damage_state_ind[:, comp_index] >= (len(component.frag_func.damage_states) - 1))

        return comp_resp_dict

    def get_component_types(self):
        """
        Convenience method to get the list of components that are
        costed.

        :return: list of costed component types
        """
        uncosted_comptypes = set(
            ['CONN_NODE', 'SYSTEM_INPUT', 'SYSTEM_OUTPUT'])

        component_types = set()

        for component in self.components.itervalues():
            if component.component_type not in uncosted_comptypes:
                component_types.add(component.component_type)

        return list(component_types)

    def get_components_for_type(self, component_type):
        """
        Return a list of components for the passed component type.
        :param component_type: A string representing a component type
        :return: List of components with the matching component type.
        """
        for component in self.components.itervalues():
            if component.component_type == component_type:
                yield component.component_id

    def get_system_damage_states(self):
        """
        Return a list of the damage state labels
        :return: List of strings detailing the system damage levels.
        """
        return [
            'DS0 None', 'DS1 Slight', 'DS2 Moderate', 'DS3 Extensive',
            'DS4 Complete'
        ]

    def get_dmg_scale_bounds(self, scenario):
        """
        An array of damage scale boundaries
        :param scenario: The values for the simulation scenarios
        :return:  Array of real numbers representing damage state boundaries
        """
        # todo introduce system subclass to infrastructure
        return [0.01, 0.15, 0.4, 0.8, 1.0]

    def get_component_class_list(self):
        """
        Return the list of component classes from the components.
        Not sure why duplicates are returned and then stripped out,
        it seems unnecessary
        :return: A generator for the list.
        """
        for component in self.components.itervalues():
            yield component.component_class
Example #14
0
class DamageState(ReponseModel):
    damage_state_description = Element('str', 'A description of what the damage state means.')
Example #15
0
class Infrastructure(Base):
    """
    The top level representation of a system that can respond to a
    range of hazards. It encapsulates a number of components that
    are used to estimate the response to various hazard levels.
    """
    supply_nodes = Element(
        'dict', 'The components that supply '
        'the infrastructure system', dict)
    output_nodes = Element(
        'dict', 'The components that output from '
        'the infrastructure system', dict)

    # supply_total = None
    _component_graph = None
    if_nominal_output = None
    system_class = None

    sys_dmg_states = None

    def __init__(self, **kwargs):
        """
        Construct the infrastructure object
        :param kwargs: Objects making up the infrastructure
        """
        super(Infrastructure, self).__init__(**kwargs)

        if not getattr(self, "components", None):
            self.components = IODict()

        self._component_graph = ComponentGraph(self.components)

    def calc_output_loss(self, scenario, component_damage_state_ind):
        """
        Calculate the results to the infrastructure given the damage state
        parameter
        :param scenario: Details of the scenario being run
        :param component_damage_state_ind:  The array of the component's
                                            damage state samples
        :return: 5 lists of calculations
        """
        # Component loss caused by the damage
        if_level_loss = np.zeros((scenario.num_samples, len(self.components)),
                                 dtype=np.float64)
        # Infrastructure loss: sum of component loss
        if_level_economic_loss = np.zeros(scenario.num_samples,
                                          dtype=np.float64)
        # Component functionality
        if_level_functionality = \
            np.zeros((scenario.num_samples, len(self.components)),
                     dtype=np.float64)
        # output for the level of damage
        if_level_output = \
            np.zeros((scenario.num_samples, len(self.output_nodes)),
                     dtype=np.float64)

        # ********************
        # NOT YET IMPLEMENTED:
        # output available as recovery progresses
        # if_output_given_recovery = \
        #     np.zeros((scenario.num_samples, scenario.num_time_steps),
        #              dtype=np.float64)

        # iterate through the samples
        for sample_index in range(scenario.num_samples):
            # initialise the function and loss arrays for the sample
            # component_function_at_time = []
            comp_sample_loss = np.zeros(len(self.components))
            comp_sample_func = np.zeros(len(self.components))
            # Extract the array of damage states for this sample

            component_ds = component_damage_state_ind[sample_index, :]
            # iterate through the components
            count = 0
            for component_index, comp_key in \
                    enumerate(sorted(self.components.keys())):
                component = self.components[comp_key]
                # get the damage state for the component
                damage_state = \
                    component.get_damage_state(component_ds[component_index])
                # use the damage state attributes to calculate the loss and
                # functionality for the component sample
                loss = damage_state.damage_ratio * component.cost_fraction
                comp_sample_loss[component_index] = loss
                comp_sample_func[component_index] = damage_state.functionality

            # save this sample's component loss and functionality
            if_level_loss[sample_index, :] = comp_sample_loss
            if_level_functionality[sample_index, :] = comp_sample_func
            # the infrastructure's economic loss for this sample is the sum
            # of all component losses
            if_level_economic_loss[sample_index] = np.sum(comp_sample_loss)
            # estimate the output for this sample's component functionality
            if_level_output[sample_index, :] \
                = self.compute_output_given_ds(comp_sample_func)

        return if_level_loss, \
               if_level_functionality, \
               if_level_output, \
               if_level_economic_loss

    def get_nominal_output(self):
        """
        Estimate the output of the undamaged infrastructure output
        nodes.
        :return: Numeric value of aggregated output.
        """
        if not self.if_nominal_output:
            self.if_nominal_output = 0
            for output_comp_id, output_comp in self.output_nodes.items():
                self.if_nominal_output += output_comp['output_node_capacity']

        return self.if_nominal_output

    def compute_output_given_ds(self, comp_level_func):
        """
        Using the graph of components, the output is calculated
        from the component functionality parameter.
        :param comp_level_func: An array that indicates the functionality
                                level for each component.
        :return: An array of the output level for each output node.
        """
        # Create the component graph if one does not yet exist
        if not self._component_graph:
            self._component_graph = ComponentGraph(self.components,
                                                   comp_level_func)
        else:
            self._component_graph.update_capacity(self.components,
                                                  comp_level_func)

        # calculate the capacity
        # system_flows_sample = []
        system_outflows_sample = np.zeros(len(self.output_nodes))
        for output_index, (output_comp_id, output_comp) in \
                enumerate(self.output_nodes.items()):
            # track the outputs by source type (e.g. water or coal)
            total_supply_flow_by_source = {}
            for supply_index, (supply_comp_id, supply_comp) in \
                    enumerate(self.supply_nodes.items()):
                if_flow_fraction = self._component_graph.maxflow(
                    supply_comp_id, output_comp_id)
                if_sample_flow = if_flow_fraction * \
                                 supply_comp['capacity_fraction']

                if supply_comp['commodity_type'] not in \
                        total_supply_flow_by_source:
                    total_supply_flow_by_source[supply_comp['commodity_type']] \
                        = if_sample_flow
                else:
                    total_supply_flow_by_source[supply_comp['commodity_type']] \
                        += if_sample_flow

            total_available_flow = min(total_supply_flow_by_source.values())

            estimated_capacity_fraction \
                = min(total_available_flow, output_comp['capacity_fraction'])
            system_outflows_sample[output_index] \
                = estimated_capacity_fraction * self.get_nominal_output()

        return system_outflows_sample

    # TODO: FIX `calc_recov_time_given_comp_ds`
    # def calc_recov_time_given_comp_ds(self, component, damage_state, scenario):
    #     '''
    #     Calculates the recovery time of a component, given damage state index
    #     '''
    #     import scipy.stats as stats
    #     recovery_parameters = component.get_recovery(damage_state)
    #     damage_parameters = component.get_damage_state(damage_state)
    #
    #     m = recovery_parameters.recovery_mean
    #     s = recovery_parameters.recovery_std
    #     fn = damage_parameters.functionality
    #     cdf = stats.norm.cdf(scenario.restoration_time_range, loc=m, scale=s)
    #     return cdf + (1.0 - cdf) * fn

    def calc_response(self, component_loss, comp_sample_func,
                      component_damage_state_ind):
        """
        Convert the arrays into dicts for subsequent analysis
        :param component_loss: The array of component loss values
        :param comp_sample_func: The array of component functionality values
        :param component_damage_state_ind: The array of component damage state
            indicators
        :return: A dict of component response statistics
        """
        component_list_sorted = np.sort(self.components.keys())
        num_samples = np.shape(component_loss)[0]
        comp_resp_dict = dict()
        comptype_resp_dict = dict()
        # ---------------------------------------------------------------
        # Collate response of individual components:
        for comp_index, comp_id in enumerate(component_list_sorted):
            component = self.components[comp_id]

            comp_resp_dict[(comp_id, 'loss_mean')] \
                = np.mean(component_loss[:, comp_index])

            comp_resp_dict[(comp_id, 'loss_std')] \
                = np.std(component_loss[:, comp_index])

            comp_resp_dict[(comp_id, 'func_mean')] \
                = np.mean(comp_sample_func[:, comp_index])

            comp_resp_dict[(comp_id, 'func_std')] \
                = np.std(comp_sample_func[:, comp_index])

            comp_resp_dict[(comp_id, 'num_failures')] \
                = np.mean(component_damage_state_ind[:, comp_index]
                          >= (len(component.damage_states) - 1))

        # ---------------------------------------------------------------
        # Collate aggregate response of component grouped by their type:
        for ct_index, ct_id in enumerate(self.get_component_types()):
            comps_of_a_type = sorted(list(self.get_components_for_type(ct_id)))
            ct_pos_index = [
                list(component_list_sorted).index(x) for x in comps_of_a_type
            ]

            comptype_resp_dict[(ct_id, 'loss_mean')] \
                = np.mean(component_loss[:, ct_pos_index])

            comptype_resp_dict[(ct_id, 'loss_std')] \
                = np.std(component_loss[:, ct_pos_index])

            comptype_resp_dict[(ct_id, 'loss_tot')] \
                = np.sum(component_loss[:, ct_pos_index]) / num_samples

            comptype_resp_dict[(ct_id, 'func_mean')] \
                = np.mean(comp_sample_func[:, ct_pos_index])

            comptype_resp_dict[(ct_id, 'func_std')] \
                = np.std(comp_sample_func[:, ct_pos_index])

            acomponent = self.components[comps_of_a_type[0]]
            comptype_resp_dict[(ct_id, 'num_failures')] \
                = np.mean(component_damage_state_ind[:, ct_pos_index]
                          >= (len(acomponent.damage_states) - 1))
        # ---------------------------------------------------------------
        return comp_resp_dict, comptype_resp_dict

    def get_component_types(self):
        """
        Convenience method to get the list of components that are
        costed.

        :return: list of costed component types
        """
        uncosted_comptypes = {'CONN_NODE', 'SYSTEM_INPUT', 'SYSTEM_OUTPUT'}

        component_types = set()

        for component in self.components.values():
            if component.component_type not in uncosted_comptypes:
                component_types.add(component.component_type)

        return list(component_types)

    def get_components_for_type(self, component_type):
        """
        Return a list of components for the passed component type.
        :param component_type: A string representing a component type
        :return: List of components with the matching component type.
        """
        for component in self.components.values():
            if component.component_type == component_type:
                yield component.component_id

    def get_system_damage_states(self):
        """
        Return a list of the damage state labels
        :return: List of strings detailing the system damage levels.
        """
        return self.sys_dmg_states

    def get_dmg_scale_bounds(self, scenario):
        """
        An array of damage scale boundaries
        :param scenario: The values for the simulation scenarios
        :return:  Array of real numbers representing damage state boundaries
        """
        # todo introduce system subclass to infrastructure
        return [0.01, 0.15, 0.4, 0.8, 1.0]

    def get_component_class_list(self):
        """
        Return the list of component classes from the components.
        Not sure why duplicates are returned and then stripped out,
        it seems unnecessary
        :return: A generator for the list.
        """
        for component in self.components.values():
            yield component.component_class
Example #16
0
class Component(Base):
    frag_func = Element('ResponseModel', 'A fragility function',
                        Element.NO_DEFAULT)

    def expose_to(self, hazard_level, scenario):
        return self.frag_func(hazard_level)