<commit_before># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2014-2020 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

import logging
import numpy
from openquake.baselib import hdf5
from openquake.baselib.general import AccumDict
from openquake.hazardlib.stats import set_rlzs_stats
from openquake.calculators import base, views

U16 = numpy.uint16
U32 = numpy.uint32
F32 = numpy.float32
F64 = numpy.float64

def floats_in(numbers):
    """
    :param numbers: an array of numbers
    :returns: number of non-uint32 number
    """
    return (U32(numbers) != numbers).sum()

def bin_ddd(fractions, n, seed):
    """
    Converting fractions into discrete damage distributions using bincount
    and numpy.random.choice
    """
    n = int(n)
    D = fractions.shape[1]  # shape (E, D)
    ddd = numpy.zeros(fractions.shape, U32)
    numpy.random.seed(seed)
    for e, frac in enumerate(fractions):
        ddd[e] = numpy.bincount(
            numpy.random.choice(D, n, p=frac/frac.sum()), minlength=D)
    return ddd

def scenario_damage(riskinputs, param, monitor):
    """
    Core function for a damage computation.

    :param riskinputs:
        :class:`openquake.risklib.riskinput.RiskInput` objects
    :param monitor:
        :class:`openquake.baselib.performance.Monitor` instance
    :param param:
        dictionary of extra parameters
    :returns:
        a dictionary {'d_asset': [(l, r, a, mean-stddev), ...],
                      'd_event': dict eid -> array of shape (L, D)
                      + optional consequences}

    `d_asset` and `d_tag` are related to the damage distributions.
    """
    crmodel = monitor.read('crmodel')
    L = len(crmodel.loss_types)
    D = len(crmodel.damage_states)
    consequences = crmodel.get_consequences()
    # algorithm used to compute the discrete damage distributions
    approx_ddd = param['approx_ddd']
    z = numpy.zeros((L, D - 1), F32 if approx_ddd else U32)
    d_event = AccumDict(accum=z)
    res = {'d_event': d_event, 'd_asset': []}
    for name in consequences:
        res['avg_' + name] = []
        res[name + '_by_event'] = AccumDict(accum=numpy.zeros(L, F64))
        # using F64 here is necessary: with F32 the non-commutativity
        # of addition would hurt too much with multiple tasks
    seed = param['master_seed']
    num_events = param['num_events']  # per realization
    for ri in riskinputs:
        # here instead F32 floats are ok
        ddic = AccumDict(accum=numpy.zeros((L, D - 1), F32))  # aid,eid->dd
        ri.hazard_getter.init()
        for out in ri.gen_outputs(crmodel, monitor):
            r = out.rlzi
            ne = num_events[r]  # total number of events
            for l, loss_type in enumerate(crmodel.loss_types):
                for asset, fractions in zip(ri.assets, out[loss_type]):
                    aid = asset['ordinal']
                    if approx_ddd:
                        ddds = fractions * asset['number']
                    else:
                        ddds = bin_ddd(
                            fractions, asset['number'], seed + aid)
                    # ddds has shape E', D with E' == len(out.eids)
                    for e, ddd in enumerate(ddds):
                        eid = out.eids[e]
                        ddic[aid, eid][l] += ddd[1:]
                        d_event[eid][l] += ddd[1:]
                    tot = ddds.sum(axis=0)  # shape D
                    nodamage = asset['number'] * (ne - len(ddds))
                    tot[0] += nodamage
                    res['d_asset'].append((l, r, aid, tot))
                    # TODO: use the ddd, not the fractions in compute_csq
                    csq = crmodel.compute_csq(asset, fractions, loss_type)
                    for name, values in csq.items():
                        res['avg_%s' % name].append(
                            (l, r, asset['ordinal'], values.sum(axis=0)))
                        by_event = res[name + '_by_event']
                        for eid, value in zip(out.eids, values):
                            by_event[eid][l] += value

        res['aed'] = aed = numpy.zeros(len(ddic), param['asset_damage_dt'])
        for i, ((aid, eid), dd) in enumerate(sorted(ddic.items())):
            damages = [dd[l, d] for l in range(L) for d in range(D-1)]
            aed[i] = (aid, eid) + tuple(damages)
    return res

@base.calculators.add('scenario_damage', 'event_based_damage')
class ScenarioDamageCalculator(base.RiskCalculator):
    """
    Damage calculator
    """
    core_task = scenario_damage
    is_stochastic = True
    precalc = 'event_based'
    accept_precalc = ['scenario', 'event_based', 'event_based_risk']

    def pre_execute(self):
        super().pre_execute()
        num_floats = floats_in(self.assetcol['number'])
        if num_floats:
            logging.warning(
                'The exposure contains %d non-integer asset numbers: '
                'using floating point damage distributions', num_floats)
        bad = self.assetcol['number'] > 2**32 - 1
        for ass in self.assetcol[bad]:
            aref = self.assetcol.tagcol.id[ass['id']]
            logging.error("The asset %s has number=%s > 2^32-1!",
                          aref, ass['number'])
        self.param['approx_ddd'] = self.oqparam.approx_ddd or num_floats
        self.param['asset_damage_dt'] = self.crmodel.asset_damage_dt(
            self.oqparam.approx_ddd or num_floats)
        self.param['master_seed'] = self.oqparam.master_seed
        self.param['num_events'] = numpy.bincount(  # events by rlz
            self.datastore['events']['rlz_id'])
        fields = []
        for f, dt in self.param['asset_damage_dt']:
            self.datastore.create_dset('dd_data/' + f, dt, compression='gzip')
            fields.append(f)
        attrs = self.datastore.getitem('dd_data').attrs
        attrs['__pdcolumns__'] = ' '.join(fields)
        self.riskinputs = self.build_riskinputs('gmf')

    def combine(self, acc, res):
        with self.monitor('saving dd_data', measuremem=True):
            aed = res.pop('aed', ())
            if len(aed) == 0:
                return acc + res
            for name in aed.dtype.names:
                hdf5.extend(self.datastore['dd_data/' + name], aed[name])
            return acc + res

    def post_execute(self, result):
        """
        Compute stats for the aggregated distributions and save
        the results on the datastore.
        """
        if not result:
            self.collapsed()
            return
        dstates = self.crmodel.damage_states
        ltypes = self.crmodel.loss_types
        L = self.L = len(ltypes)
        R = self.R
        D = len(dstates)
        A = len(self.assetcol)
        if not len(self.datastore['dd_data/aid']):
            logging.warning('There is no damage at all!')

        # avg_ratio = ratio used when computing the averages
        oq = self.oqparam
        if oq.investigation_time:  # event_based_damage
            avg_ratio = numpy.array([oq.ses_ratio] * R)
        else:  # scenario_damage
            avg_ratio = 1. / self.param['num_events']

        # damage by asset
        d_asset = numpy.zeros((A, R, L, D), F32)
        for (l, r, a, tot) in result['d_asset']:
            d_asset[a, r, l] = tot * avg_ratio[r]
        self.datastore['damages-rlzs'] = d_asset
        set_rlzs_stats(self.datastore,
                       'damages',
                       asset_id=self.assetcol['id'],
                       loss_type=oq.loss_names,
                       dmg_state=dstates)
        logging.info('\n' + views.view('portfolio_damage', self.datastore))
        self.sanity_check()

        # damage by event: make sure the sum of the buildings is consistent
        tot = self.assetcol['number'].sum()
        dt = F32 if self.param['approx_ddd'] else U32
        dbe = numpy.zeros((self.E, L, D), dt)  # shape E, L, D
        dbe[:, :, 0] = tot
        for e, dmg_by_lt in result['d_event'].items():
            for l, dmg in enumerate(dmg_by_lt):
                dbe[e, l,  0] = tot - dmg.sum()
                dbe[e, l,  1:] = dmg
        self.datastore['dmg_by_event'] = dbe

        # consequence distributions
        del result['d_asset']
        del result['d_event']
        dtlist = [('event_id', U32), ('rlz_id', U16), ('loss', (F32, (L,)))]
        rlz = self.datastore['events']['rlz_id']
        for name, csq in result.items():
            if name.startswith('avg_'):
                c_asset = numpy.zeros((A, R, L), F32)
                for (l, r, a, stat) in result[name]:
                    c_asset[a, r, l] = stat * avg_ratio[r]
                self.datastore[name + '-rlzs'] = c_asset
                set_rlzs_stats(self.datastore, name,
                               asset_id=self.assetcol['id'],
                               loss_type=oq.loss_names)
            elif name.endswith('_by_event'):
                arr = numpy.zeros(len(csq), dtlist)
                for i, (eid, loss) in enumerate(csq.items()):
                    arr[i] = (eid, rlz[eid], loss)
                self.datastore[name] = arr

    def sanity_check(self):
        """
        Sanity check on the total number of assets
        """
        if self.R == 1:
            avgdamages = self.datastore.sel('damages-rlzs')
        else:
            avgdamages = self.datastore.sel(
                'damages-stats', stat='mean')
        num_assets = avgdamages.sum(axis=(0, 1, 3))  # by loss_type
        expected = self.assetcol['number'].sum()
        nums = set(num_assets) | {expected}
        if len(nums) > 1:
            numdic = dict(expected=expected)
            for lt, num in zip(self.oqparam.loss_names, num_assets):
                numdic[lt] = num
            logging.info('Due to numeric errors the total number of assets'
                         ' is imprecise: %s', numdic)
<commit_msg>Temporary reversion<commit_after># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2014-2020 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

import logging
import numpy
from openquake.baselib import hdf5
from openquake.baselib.general import AccumDict
from openquake.hazardlib.stats import set_rlzs_stats
from openquake.calculators import base, views

U16 = numpy.uint16
U32 = numpy.uint32
F32 = numpy.float32
F64 = numpy.float64

def floats_in(numbers):
    """
    :param numbers: an array of numbers
    :returns: number of non-uint32 number
    """
    return (U32(numbers) != numbers).sum()

def bin_ddd(fractions, n, seed):
    """
    Converting fractions into discrete damage distributions using bincount
    and numpy.random.choice
    """
    n = int(n)
    D = fractions.shape[1]  # shape (E, D)
    ddd = numpy.zeros(fractions.shape, U32)
    numpy.random.seed(seed)
    for e, frac in enumerate(fractions):
        ddd[e] = numpy.bincount(
            numpy.random.choice(D, n, p=frac/frac.sum()), minlength=D)
    return ddd

def scenario_damage(riskinputs, param, monitor):
    """
    Core function for a damage computation.

    :param riskinputs:
        :class:`openquake.risklib.riskinput.RiskInput` objects
    :param monitor:
        :class:`openquake.baselib.performance.Monitor` instance
    :param param:
        dictionary of extra parameters
    :returns:
        a dictionary {'d_asset': [(l, r, a, mean-stddev), ...],
                      'd_event': dict eid -> array of shape (L, D)
                      + optional consequences}

    `d_asset` and `d_tag` are related to the damage distributions.
    """
    crmodel = monitor.read('crmodel')
    L = len(crmodel.loss_types)
    D = len(crmodel.damage_states)
    consequences = crmodel.get_consequences()
    # algorithm used to compute the discrete damage distributions
    approx_ddd = param['approx_ddd']
    z = numpy.zeros((L, D - 1), F32 if approx_ddd else U32)
    d_event = AccumDict(accum=z)
    res = {'d_event': d_event, 'd_asset': []}
    for name in consequences:
        res['avg_' + name] = []
        res[name + '_by_event'] = AccumDict(accum=numpy.zeros(L, F64))
        # using F64 here is necessary: with F32 the non-commutativity
        # of addition would hurt too much with multiple tasks
    seed = param['master_seed']
    num_events = param['num_events']  # per realization
    for ri in riskinputs:
        # here instead F32 floats are ok
        ddic = AccumDict(accum=numpy.zeros((L, D - 1), F32))  # aid,eid->dd
        ri.hazard_getter.init()
        for out in ri.gen_outputs(crmodel, monitor):
            r = out.rlzi
            ne = num_events[r]  # total number of events
            for l, loss_type in enumerate(crmodel.loss_types):
                for asset, fractions in zip(ri.assets, out[loss_type]):
                    aid = asset['ordinal']
                    if approx_ddd:
                        ddds = fractions * asset['number']
                    else:
                        ddds = bin_ddd(
                            fractions, asset['number'], seed + aid)
                    # ddds has shape E', D with E' == len(out.eids)
                    for e, ddd in enumerate(ddds):
                        eid = out.eids[e]
                        ddic[aid, eid][l] = ddd[1:]
                        d_event[eid][l] += ddd[1:]
                    tot = ddds.sum(axis=0)  # shape D
                    nodamage = asset['number'] * (ne - len(ddds))
                    tot[0] += nodamage
                    res['d_asset'].append((l, r, aid, tot))
                    # TODO: use the ddd, not the fractions in compute_csq
                    csq = crmodel.compute_csq(asset, fractions, loss_type)
                    for name, values in csq.items():
                        res['avg_%s' % name].append(
                            (l, r, asset['ordinal'], values.sum(axis=0)))
                        by_event = res[name + '_by_event']
                        for eid, value in zip(out.eids, values):
                            by_event[eid][l] += value

        res['aed'] = aed = numpy.zeros(len(ddic), param['asset_damage_dt'])
        for i, ((aid, eid), dd) in enumerate(sorted(ddic.items())):
            damages = [dd[l, d] for l in range(L) for d in range(D-1)]
            aed[i] = (aid, eid) + tuple(damages)
    return res

@base.calculators.add('scenario_damage', 'event_based_damage')
class ScenarioDamageCalculator(base.RiskCalculator):
    """
    Damage calculator
    """
    core_task = scenario_damage
    is_stochastic = True
    precalc = 'event_based'
    accept_precalc = ['scenario', 'event_based', 'event_based_risk']

    def pre_execute(self):
        super().pre_execute()
        num_floats = floats_in(self.assetcol['number'])
        if num_floats:
            logging.warning(
                'The exposure contains %d non-integer asset numbers: '
                'using floating point damage distributions', num_floats)
        bad = self.assetcol['number'] > 2**32 - 1
        for ass in self.assetcol[bad]:
            aref = self.assetcol.tagcol.id[ass['id']]
            logging.error("The asset %s has number=%s > 2^32-1!",
                          aref, ass['number'])
        self.param['approx_ddd'] = self.oqparam.approx_ddd or num_floats
        self.param['asset_damage_dt'] = self.crmodel.asset_damage_dt(
            self.oqparam.approx_ddd or num_floats)
        self.param['master_seed'] = self.oqparam.master_seed
        self.param['num_events'] = numpy.bincount(  # events by rlz
            self.datastore['events']['rlz_id'])
        fields = []
        for f, dt in self.param['asset_damage_dt']:
            self.datastore.create_dset('dd_data/' + f, dt, compression='gzip')
            fields.append(f)
        attrs = self.datastore.getitem('dd_data').attrs
        attrs['__pdcolumns__'] = ' '.join(fields)
        self.riskinputs = self.build_riskinputs('gmf')

    def combine(self, acc, res):
        with self.monitor('saving dd_data', measuremem=True):
            aed = res.pop('aed', ())
            if len(aed) == 0:
                return acc + res
            for name in aed.dtype.names:
                hdf5.extend(self.datastore['dd_data/' + name], aed[name])
            return acc + res

    def post_execute(self, result):
        """
        Compute stats for the aggregated distributions and save
        the results on the datastore.
        """
        if not result:
            self.collapsed()
            return
        dstates = self.crmodel.damage_states
        ltypes = self.crmodel.loss_types
        L = self.L = len(ltypes)
        R = self.R
        D = len(dstates)
        A = len(self.assetcol)
        if not len(self.datastore['dd_data/aid']):
            logging.warning('There is no damage at all!')

        # avg_ratio = ratio used when computing the averages
        oq = self.oqparam
        if oq.investigation_time:  # event_based_damage
            avg_ratio = numpy.array([oq.ses_ratio] * R)
        else:  # scenario_damage
            avg_ratio = 1. / self.param['num_events']

        # damage by asset
        d_asset = numpy.zeros((A, R, L, D), F32)
        for (l, r, a, tot) in result['d_asset']:
            d_asset[a, r, l] = tot * avg_ratio[r]
        self.datastore['damages-rlzs'] = d_asset
        set_rlzs_stats(self.datastore,
                       'damages',
                       asset_id=self.assetcol['id'],
                       loss_type=oq.loss_names,
                       dmg_state=dstates)
        logging.info('\n' + views.view('portfolio_damage', self.datastore))
        self.sanity_check()

        # damage by event: make sure the sum of the buildings is consistent
        tot = self.assetcol['number'].sum()
        dt = F32 if self.param['approx_ddd'] else U32
        dbe = numpy.zeros((self.E, L, D), dt)  # shape E, L, D
        dbe[:, :, 0] = tot
        for e, dmg_by_lt in result['d_event'].items():
            for l, dmg in enumerate(dmg_by_lt):
                dbe[e, l,  0] = tot - dmg.sum()
                dbe[e, l,  1:] = dmg
        self.datastore['dmg_by_event'] = dbe

        # consequence distributions
        del result['d_asset']
        del result['d_event']
        dtlist = [('event_id', U32), ('rlz_id', U16), ('loss', (F32, (L,)))]
        rlz = self.datastore['events']['rlz_id']
        for name, csq in result.items():
            if name.startswith('avg_'):
                c_asset = numpy.zeros((A, R, L), F32)
                for (l, r, a, stat) in result[name]:
                    c_asset[a, r, l] = stat * avg_ratio[r]
                self.datastore[name + '-rlzs'] = c_asset
                set_rlzs_stats(self.datastore, name,
                               asset_id=self.assetcol['id'],
                               loss_type=oq.loss_names)
            elif name.endswith('_by_event'):
                arr = numpy.zeros(len(csq), dtlist)
                for i, (eid, loss) in enumerate(csq.items()):
                    arr[i] = (eid, rlz[eid], loss)
                self.datastore[name] = arr

    def sanity_check(self):
        """
        Sanity check on the total number of assets
        """
        if self.R == 1:
            avgdamages = self.datastore.sel('damages-rlzs')
        else:
            avgdamages = self.datastore.sel(
                'damages-stats', stat='mean')
        num_assets = avgdamages.sum(axis=(0, 1, 3))  # by loss_type
        expected = self.assetcol['number'].sum()
        nums = set(num_assets) | {expected}
        if len(nums) > 1:
            numdic = dict(expected=expected)
            for lt, num in zip(self.oqparam.loss_names, num_assets):
                numdic[lt] = num
            logging.info('Due to numeric errors the total number of assets'
                         ' is imprecise: %s', numdic)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2014-2018 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

import itertools
import logging
import numpy

from openquake.risklib import scientific, riskmodels
from openquake.calculators import base, event_based

F32 = numpy.float32
F64 = numpy.float64

def dist_by_asset(data, multi_stat_dt, number):
    """
    :param data: array of shape (N, R, L, 2, ...)
    :param multi_stat_dt: numpy dtype for statistical outputs
    :param number: expected number of units per asset
    :returns: array of shape (N, R) with records of type multi_stat_dt
    """
    N, R, L = data.shape[:3]
    out = numpy.zeros((N, R), multi_stat_dt)
    for l, lt in enumerate(multi_stat_dt.names):
        out_lt = out[lt]
        for n, r in itertools.product(range(N), range(R)):
            mean, stddev = data[n, r, l]
            out_lt[n, r] = (mean, stddev)
            # sanity check on the sum over all damage states
            if abs(mean.sum() / number[n] - 1) > 1E-3:
                logging.warn(
                    'Asset #%d, rlz=%d, expected %s, got %s for %s damage',
                    n, r, number[n], mean.sum(), lt)
    return out

def scenario_damage(riskinput, riskmodel, param, monitor):
    """
    Core function for a damage computation.

    :param riskinput:
        a :class:`openquake.risklib.riskinput.RiskInput` object
    :param riskmodel:
        a :class:`openquake.risklib.riskinput.CompositeRiskModel` instance
    :param monitor:
        :class:`openquake.baselib.performance.Monitor` instance
    :param param:
        dictionary of extra parameters
    :returns:
        a dictionary {'d_asset': [(l, r, a, mean-stddev), ...],
                      'd_tag': damage array of shape T, R, L, E, D,
                      'c_asset': [(l, r, a, mean-stddev), ...],
                      'c_tag': damage array of shape T, R, L, E}

    `d_asset` and `d_tag` are related to the damage distributions
    whereas `c_asset` and `c_tag` are the consequence distributions.
    If there is no consequence model `c_asset` is an empty list and
    `c_tag` is a zero-valued array.
    """
    c_models = param['consequence_models']
    L = len(riskmodel.loss_types)
    R = riskinput.hazard_getter.num_rlzs
    D = len(riskmodel.damage_states)
    E = param['number_of_ground_motion_fields']
    T = len(param['tags'])
    result = dict(d_asset=[], d_tag=numpy.zeros((T, R, L, E, D), F64),
                  c_asset=[], c_tag=numpy.zeros((T, R, L, E), F64))
    for outputs in riskmodel.gen_outputs(riskinput, monitor):
        r = outputs.rlzi
        for l, damages in enumerate(outputs):
            loss_type = riskmodel.loss_types[l]
            c_model = c_models.get(loss_type)
            for a, fraction in enumerate(damages):
                asset = outputs.assets[a]
                taxo = riskmodel.taxonomy[asset.taxonomy]
                damages = fraction * asset.number
                t = asset.tagmask(param['tags'])
                result['d_tag'][t, r, l] += damages  # shape (E, D)
                if c_model:  # compute consequences
                    means = [par[0] for par in c_model[taxo].params]
                    # NB: we add a 0 in front for nodamage state
                    c_ratio = numpy.dot(fraction, [0] + means)
                    consequences = c_ratio * asset.value(loss_type)
                    result['c_asset'].append(
                        (l, r, asset.ordinal,
                         scientific.mean_std(consequences)))
                    result['c_tag'][t, r, l] += consequences
                    # TODO: consequences for the occupants
                result['d_asset'].append(
                    (l, r, asset.ordinal, scientific.mean_std(damages)))
    result['gmdata'] = riskinput.gmdata
    return result

@base.calculators.add('scenario_damage')
class ScenarioDamageCalculator(base.RiskCalculator):
    """
    Scenario damage calculator
    """
    pre_calculator = 'scenario'
    core_task = scenario_damage
    is_stochastic = True

    def pre_execute(self):
        if 'gmfs' in self.oqparam.inputs:
            self.pre_calculator = None
        base.RiskCalculator.pre_execute(self)
        if self.oqparam.shakemap_id or 'shakemap' in self.oqparam.inputs:
            self.read_shakemap()
            self.R = 1
        else:
            _, self.R = base.get_gmfs(self)
        E = self.oqparam.number_of_ground_motion_fields
        self.param['number_of_ground_motion_fields'] = E
        self.param['consequence_models'] = riskmodels.get_risk_models(
            self.oqparam, 'consequence')
        self.riskinputs = self.build_riskinputs('gmf', num_events=E)
        self.param['tags'] = list(self.assetcol.tagcol)

    def post_execute(self, result):
        """
        Compute stats for the aggregated distributions and save
        the results on the datastore.
        """
        dstates = self.riskmodel.damage_states
        ltypes = self.riskmodel.loss_types
        L = len(ltypes)
        R = len(self.rlzs_assoc.realizations)
        D = len(dstates)
        N = len(self.assetcol)

        # damage distributions
        dt_list = []
        for ltype in ltypes:
            dt_list.append((ltype, numpy.dtype([('mean', (F32, D)),
                                                ('stddev', (F32, D))])))
        multi_stat_dt = numpy.dtype(dt_list)
        d_asset = numpy.zeros((N, R, L, 2, D), F32)
        for (l, r, a, stat) in result['d_asset']:
            d_asset[a, r, l] = stat
        self.datastore['dmg_by_asset'] = dist_by_asset(
            d_asset, multi_stat_dt, self.assetcol.array['number'])

        # consequence distributions
        if result['c_asset']:
            stat_dt = numpy.dtype([('mean', F32), ('stddev', F32)])
            c_asset = numpy.zeros((N, R, L), stat_dt)
            for (l, r, a, stat) in result['c_asset']:
                c_asset[a, r, l] = stat
            multi_stat_dt = self.oqparam.loss_dt(stat_dt)
            self.datastore['losses_by_asset'] = c_asset

        # save gmdata
        self.gmdata = result['gmdata']
        for arr in self.gmdata.values():
            arr[-2] = self.oqparam.number_of_ground_motion_fields  # events
        event_based.save_gmdata(self, R)
<commit_msg>Added dmg_by_event output [skip hazardlib]<commit_after># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2014-2018 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

import itertools
import logging
import numpy

from openquake.risklib import scientific, riskmodels
from openquake.calculators import base, event_based

F32 = numpy.float32
F64 = numpy.float64

def dist_by_asset(data, multi_stat_dt, number):
    """
    :param data: array of shape (N, R, L, 2, ...)
    :param multi_stat_dt: numpy dtype for statistical outputs
    :param number: expected number of units per asset
    :returns: array of shape (N, R) with records of type multi_stat_dt
    """
    N, R, L = data.shape[:3]
    out = numpy.zeros((N, R), multi_stat_dt)
    for l, lt in enumerate(multi_stat_dt.names):
        out_lt = out[lt]
        for n, r in itertools.product(range(N), range(R)):
            mean, stddev = data[n, r, l]
            out_lt[n, r] = (mean, stddev)
            # sanity check on the sum over all damage states
            if abs(mean.sum() / number[n] - 1) > 1E-3:
                logging.warn(
                    'Asset #%d, rlz=%d, expected %s, got %s for %s damage',
                    n, r, number[n], mean.sum(), lt)
    return out

def scenario_damage(riskinput, riskmodel, param, monitor):
    """
    Core function for a damage computation.

    :param riskinput:
        a :class:`openquake.risklib.riskinput.RiskInput` object
    :param riskmodel:
        a :class:`openquake.risklib.riskinput.CompositeRiskModel` instance
    :param monitor:
        :class:`openquake.baselib.performance.Monitor` instance
    :param param:
        dictionary of extra parameters
    :returns:
        a dictionary {'d_asset': [(l, r, a, mean-stddev), ...],
                      'd_event': damage array of shape R, L, E, D,
                      'c_asset': [(l, r, a, mean-stddev), ...],
                      'c_event': damage array of shape R, L, E}

    `d_asset` and `d_tag` are related to the damage distributions
    whereas `c_asset` and `c_tag` are the consequence distributions.
    If there is no consequence model `c_asset` is an empty list and
    `c_tag` is a zero-valued array.
    """
    c_models = param['consequence_models']
    L = len(riskmodel.loss_types)
    R = riskinput.hazard_getter.num_rlzs
    D = len(riskmodel.damage_states)
    E = param['number_of_ground_motion_fields']
    result = dict(d_asset=[], d_event=numpy.zeros((E, R, L, D), F64),
                  c_asset=[], c_event=numpy.zeros((E, R, L), F64))
    for outputs in riskmodel.gen_outputs(riskinput, monitor):
        r = outputs.rlzi
        for l, damages in enumerate(outputs):
            loss_type = riskmodel.loss_types[l]
            c_model = c_models.get(loss_type)
            for a, fraction in enumerate(damages):
                asset = outputs.assets[a]
                taxo = riskmodel.taxonomy[asset.taxonomy]
                damages = fraction * asset.number
                result['d_event'][:, r, l] += damages  # shape (E, D)
                if c_model:  # compute consequences
                    means = [par[0] for par in c_model[taxo].params]
                    # NB: we add a 0 in front for nodamage state
                    c_ratio = numpy.dot(fraction, [0] + means)
                    consequences = c_ratio * asset.value(loss_type)
                    result['c_asset'].append(
                        (l, r, asset.ordinal,
                         scientific.mean_std(consequences)))
                    result['c_event'][:, r, l] += consequences
                    # TODO: consequences for the occupants
                result['d_asset'].append(
                    (l, r, asset.ordinal, scientific.mean_std(damages)))
    result['gmdata'] = riskinput.gmdata
    return result

@base.calculators.add('scenario_damage')
class ScenarioDamageCalculator(base.RiskCalculator):
    """
    Scenario damage calculator
    """
    pre_calculator = 'scenario'
    core_task = scenario_damage
    is_stochastic = True

    def pre_execute(self):
        if 'gmfs' in self.oqparam.inputs:
            self.pre_calculator = None
        base.RiskCalculator.pre_execute(self)
        if self.oqparam.shakemap_id or 'shakemap' in self.oqparam.inputs:
            self.read_shakemap()
            self.R = 1
        else:
            _, self.R = base.get_gmfs(self)
        E = self.oqparam.number_of_ground_motion_fields
        self.param['number_of_ground_motion_fields'] = E
        self.param['consequence_models'] = riskmodels.get_risk_models(
            self.oqparam, 'consequence')
        self.riskinputs = self.build_riskinputs('gmf', num_events=E)
        self.param['tags'] = list(self.assetcol.tagcol)

    def post_execute(self, result):
        """
        Compute stats for the aggregated distributions and save
        the results on the datastore.
        """
        dstates = self.riskmodel.damage_states
        ltypes = self.riskmodel.loss_types
        L = len(ltypes)
        R = len(self.rlzs_assoc.realizations)
        D = len(dstates)
        N = len(self.assetcol)
        E = self.oqparam.number_of_ground_motion_fields

        # damage distributions
        dt_list = []
        for ltype in ltypes:
            dt_list.append((ltype, numpy.dtype([('mean', (F32, D)),
                                                ('stddev', (F32, D))])))
        multi_stat_dt = numpy.dtype(dt_list)
        d_asset = numpy.zeros((N, R, L, 2, D), F32)
        for (l, r, a, stat) in result['d_asset']:
            d_asset[a, r, l] = stat
        self.datastore['dmg_by_asset'] = dist_by_asset(
            d_asset, multi_stat_dt, self.assetcol.array['number'])
        dmg_dt = [(ds, F64) for ds in self.riskmodel.damage_states]
        d_event = numpy.zeros((E, R, L), dmg_dt)
        for d, ds in enumerate(self.riskmodel.damage_states):
            d_event[ds] = result['d_event'][:, :, :, d]
        self.datastore['dmg_by_event'] = d_event

        # consequence distributions
        if result['c_asset']:
            stat_dt = numpy.dtype([('mean', F32), ('stddev', F32)])
            c_asset = numpy.zeros((N, R, L), stat_dt)
            for (l, r, a, stat) in result['c_asset']:
                c_asset[a, r, l] = stat
            multi_stat_dt = self.oqparam.loss_dt(stat_dt)
            self.datastore['losses_by_asset'] = c_asset
            self.datastore['losses_by_event'] = result['c_event']

        # save gmdata
        self.gmdata = result['gmdata']
        for arr in self.gmdata.values():
            arr[-2] = self.oqparam.number_of_ground_motion_fields  # events
        event_based.save_gmdata(self, R)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2014-2020 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

import logging
import numpy
from openquake.baselib import hdf5
from openquake.baselib.general import AccumDict, get_indices
from openquake.calculators import base

U16 = numpy.uint16
U32 = numpy.uint32
F32 = numpy.float32
F64 = numpy.float64

def floats_in(numbers):
    """
    :param numbers: an array of numbers
    :returns: number of non-uint32 number
    """
    return (U32(numbers) != numbers).sum()

def bin_ddd(fractions, n, seed):
    """
    Converting fractions into discrete damage distributions using bincount
    and numpy.random.choice
    """
    n = int(n)
    D = fractions.shape[1]  # shape (E, D)
    ddd = numpy.zeros(fractions.shape, U32)
    numpy.random.seed(seed)
    for e, frac in enumerate(fractions):
        ddd[e] = numpy.bincount(
            numpy.random.choice(D, n, p=frac/frac.sum()), minlength=D)
    return ddd

def approx_ddd(fractions, n, seed=None):
    """
    Converting fractions into uint16 discrete damage distributions using round
    """
    ddd = U32(numpy.round(fractions * n))  # shape (E, D)
    # fix the no-damage discrete damage distributions by making sure
    # that the total sum is n: nodamage = n - sum(others)
    ddd[:, 0] = n - ddd[:, 1:].sum(axis=1)
    return ddd

def scenario_damage(riskinputs, crmodel, param, monitor):
    """
    Core function for a damage computation.

    :param riskinputs:
        :class:`openquake.risklib.riskinput.RiskInput` objects
    :param crmodel:
        a :class:`openquake.risklib.riskinput.CompositeRiskModel` instance
    :param monitor:
        :class:`openquake.baselib.performance.Monitor` instance
    :param param:
        dictionary of extra parameters
    :returns:
        a dictionary {'d_asset': [(l, r, a, mean-stddev), ...],
                      'd_event': dict eid -> array of shape (L, D)
                      + optional consequences}

    `d_asset` and `d_tag` are related to the damage distributions.
    """
    L = len(crmodel.loss_types)
    D = len(crmodel.damage_states)
    consequences = crmodel.get_consequences()
    haz_mon = monitor('getting hazard', measuremem=False)
    rsk_mon = monitor('aggregating risk', measuremem=False)
    d_event = AccumDict(accum=numpy.zeros((L, D - 1), U32))
    res = {'d_event': d_event}
    for name in consequences:
        res[name + '_by_event'] = AccumDict(accum=numpy.zeros(L, F64))
        # using F64 here is necessary: with F32 the non-commutativity
        # of addition would hurt too much with multiple tasks
    seed = param['master_seed']
    # algorithm used to compute the discrete damage distributions
    make_ddd = approx_ddd if param['approx_ddd'] else bin_ddd
    for ri in riskinputs:
        # otherwise test 4b will randomly break with last digit changes
        # in dmg_by_event :-(
        result = dict(d_asset=[])
        for name in consequences:
            result['avg_' + name] = []
        ddic = AccumDict(accum=numpy.zeros((L, D - 1), F32))  # aid,eid->dd
        with haz_mon:
            ri.hazard_getter.init()
        for out in ri.gen_outputs(crmodel, monitor):
            with rsk_mon:
                r = out.rlzi
                for l, loss_type in enumerate(crmodel.loss_types):
                    for asset, fractions in zip(ri.assets, out[loss_type]):
                        aid = asset['ordinal']
                        ddds = make_ddd(fractions, asset['number'], seed + aid)
                        for e, ddd in enumerate(ddds):
                            eid = out.eids[e]
                            ddic[aid, eid][l] = ddd[1:]
                            d_event[eid][l] += ddd[1:]
                        if make_ddd is approx_ddd:
                            tot = (fractions * asset['number']).sum(axis=0)
                        else:
                            tot = ddds.sum(axis=0)
                        result['d_asset'].append((l, r, asset['ordinal'], tot))
                        # TODO: use the ddd, not the fractions in compute_csq
                        csq = crmodel.compute_csq(asset, fractions, loss_type)
                        for name, values in csq.items():
                            result['avg_%s' % name].append(
                                (l, r, asset['ordinal'], values.sum(axis=0)))
                            by_event = res[name + '_by_event']
                            for eid, value in zip(out.eids, values):
                                by_event[eid][l] += value
        with rsk_mon:
            result['aed'] = aed = numpy.zeros(len(ddic), param['aed_dt'])
            for i, ((aid, eid), dd) in enumerate(sorted(ddic.items())):
                aed[i] = (aid, eid, dd)
        yield result
    yield res

@base.calculators.add('scenario_damage')
class ScenarioDamageCalculator(base.RiskCalculator):
    """
    Scenario damage calculator
    """
    core_task = scenario_damage
    is_stochastic = True
    precalc = 'scenario'
    accept_precalc = ['scenario']

    def pre_execute(self):
        super().pre_execute()
        num_floats = floats_in(self.assetcol['number'])
        if num_floats:
            logging.warning(
                'The exposure contains %d non-integer asset numbers: '
                'using floating point damage distributions', num_floats)
        bad = self.assetcol['number'] > 2**32 - 1
        for ass in self.assetcol[bad]:
            aref = self.assetcol.tagcol.id[ass['id']]
            logging.error("The asset %s has number=%s > 2^32-1!",
                          aref, ass['number'])
        self.param['approx_ddd'] = self.oqparam.approx_ddd or num_floats
        self.param['aed_dt'] = aed_dt = self.crmodel.aid_eid_dd_dt()
        self.param['master_seed'] = self.oqparam.master_seed
        A = len(self.assetcol)
        self.datastore.create_dset('dd_data/data', aed_dt, compression='gzip')
        self.datastore.create_dset('dd_data/indices', U32, (A, 2))
        self.riskinputs = self.build_riskinputs('gmf')
        self.start = 0

    def combine(self, acc, res):
        aed = res.pop('aed', ())
        if len(aed) == 0:
            return acc + res
        for aid, [(i1, i2)] in get_indices(aed['aid']).items():

            self.datastore['dd_data/indices'][aid] = (
                self.start + i1, self.start + i2)
        self.start += len(aed)
        hdf5.extend(self.datastore['dd_data/data'], aed)
        return acc + res

    def post_execute(self, result):
        """
        Compute stats for the aggregated distributions and save
        the results on the datastore.
        """
        if not result:
            self.collapsed()
            return
        dstates = self.crmodel.damage_states
        ltypes = self.crmodel.loss_types
        L = len(ltypes)
        R = self.R
        D = len(dstates)
        A = len(self.assetcol)
        indices = self.datastore['dd_data/indices'][()]
        if not len(self.datastore['dd_data/data']):
            logging.warning('There is no damage at all!')
        events_per_asset = (indices[:, 1] - indices[:, 0]).mean()
        logging.info('Found ~%d dmg distributions per asset', events_per_asset)

        # avg_ratio = ratio used when computing the averages
        oq = self.oqparam
        if oq.investigation_time is None:  # scenario_damage
            avg_ratio = 1. / oq.number_of_ground_motion_fields
        else:  # event_based_damage
            avg_ratio = oq.ses_ratio

        # damage by asset
        d_asset = numpy.zeros((A, R, L, D), F32)
        for (l, r, a, tot) in result['d_asset']:
            d_asset[a, r, l] = tot
        self.datastore['avg_damages-rlzs'] = d_asset * avg_ratio

        # damage by event: make sure the sum of the buildings is consistent
        tot = self.assetcol['number'].sum()
        dbe = numpy.zeros((self.E, L, D), U32)  # shape E, L, D
        dbe[:, :, 0] = tot
        for e, dmg_by_lt in result['d_event'].items():
            for l, dmg in enumerate(dmg_by_lt):
                dbe[e, l,  0] = tot - dmg.sum()
                dbe[e, l,  1:] = dmg
        self.datastore['dmg_by_event'] = dbe

        # consequence distributions
        del result['d_asset']
        del result['d_event']
        dtlist = [('event_id', U32), ('rlz_id', U16), ('loss', (F32, (L,)))]
        rlz = self.datastore['events']['rlz_id']
        for name, csq in result.items():
            if name.startswith('avg_'):
                c_asset = numpy.zeros((A, R, L), F32)
                for (l, r, a, stat) in result[name]:
                    c_asset[a, r, l] = stat
                self.datastore[name + '-rlzs'] = c_asset * avg_ratio
            elif name.endswith('_by_event'):
                arr = numpy.zeros(len(csq), dtlist)
                for i, (eid, loss) in enumerate(csq.items()):
                    arr[i] = (eid, rlz[eid], loss)
                self.datastore[name] = arr

@base.calculators.add('event_based_damage')
class EventBasedDamageCalculator(ScenarioDamageCalculator):
    """
    Event Based Damage calculator, able to compute avg_damages-rlzs,
    dmg_by_event and consequences.
    """
    core_task = scenario_damage
    precalc = 'event_based'
    accept_precalc = ['event_based', 'event_based_risk']
<commit_msg>Cleanup [skip CI]<commit_after># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2014-2020 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

import logging
import numpy
from openquake.baselib import hdf5
from openquake.baselib.general import AccumDict, get_indices
from openquake.calculators import base

U16 = numpy.uint16
U32 = numpy.uint32
F32 = numpy.float32
F64 = numpy.float64

def floats_in(numbers):
    """
    :param numbers: an array of numbers
    :returns: number of non-uint32 number
    """
    return (U32(numbers) != numbers).sum()

def bin_ddd(fractions, n, seed):
    """
    Converting fractions into discrete damage distributions using bincount
    and numpy.random.choice
    """
    n = int(n)
    D = fractions.shape[1]  # shape (E, D)
    ddd = numpy.zeros(fractions.shape, U32)
    numpy.random.seed(seed)
    for e, frac in enumerate(fractions):
        ddd[e] = numpy.bincount(
            numpy.random.choice(D, n, p=frac/frac.sum()), minlength=D)
    return ddd

def approx_ddd(fractions, n, seed=None):
    """
    Converting fractions into uint16 discrete damage distributions using round
    """
    ddd = U32(numpy.round(fractions * n))  # shape (E, D)
    # fix the no-damage discrete damage distributions by making sure
    # that the total sum is n: nodamage = n - sum(others)
    ddd[:, 0] = n - ddd[:, 1:].sum(axis=1)
    return ddd

def scenario_damage(riskinputs, crmodel, param, monitor):
    """
    Core function for a damage computation.

    :param riskinputs:
        :class:`openquake.risklib.riskinput.RiskInput` objects
    :param crmodel:
        a :class:`openquake.risklib.riskinput.CompositeRiskModel` instance
    :param monitor:
        :class:`openquake.baselib.performance.Monitor` instance
    :param param:
        dictionary of extra parameters
    :returns:
        a dictionary {'d_asset': [(l, r, a, mean-stddev), ...],
                      'd_event': dict eid -> array of shape (L, D)
                      + optional consequences}

    `d_asset` and `d_tag` are related to the damage distributions.
    """
    L = len(crmodel.loss_types)
    D = len(crmodel.damage_states)
    consequences = crmodel.get_consequences()
    haz_mon = monitor('getting hazard', measuremem=False)
    rsk_mon = monitor('aggregating risk', measuremem=False)
    d_event = AccumDict(accum=numpy.zeros((L, D - 1), U32))
    res = {'d_event': d_event}
    for name in consequences:
        res[name + '_by_event'] = AccumDict(accum=numpy.zeros(L, F64))
        # using F64 here is necessary: with F32 the non-commutativity
        # of addition would hurt too much with multiple tasks
    seed = param['master_seed']
    # algorithm used to compute the discrete damage distributions
    make_ddd = approx_ddd if param['approx_ddd'] else bin_ddd
    for ri in riskinputs:
        # otherwise test 4b will randomly break with last digit changes
        # in dmg_by_event :-(
        result = dict(d_asset=[])
        for name in consequences:
            result['avg_' + name] = []
        ddic = AccumDict(accum=numpy.zeros((L, D - 1), F32))  # aid,eid->dd
        with haz_mon:
            ri.hazard_getter.init()
        for out in ri.gen_outputs(crmodel, monitor):
            with rsk_mon:
                r = out.rlzi
                for l, loss_type in enumerate(crmodel.loss_types):
                    for asset, fractions in zip(ri.assets, out[loss_type]):
                        aid = asset['ordinal']
                        ddds = make_ddd(fractions, asset['number'], seed + aid)
                        for e, ddd in enumerate(ddds):
                            eid = out.eids[e]
                            ddic[aid, eid][l] = ddd[1:]
                            d_event[eid][l] += ddd[1:]
                        if make_ddd is approx_ddd:
                            tot = (fractions * asset['number']).sum(axis=0)
                        else:
                            tot = ddds.sum(axis=0)
                        result['d_asset'].append((l, r, asset['ordinal'], tot))
                        # TODO: use the ddd, not the fractions in compute_csq
                        csq = crmodel.compute_csq(asset, fractions, loss_type)
                        for name, values in csq.items():
                            result['avg_%s' % name].append(
                                (l, r, asset['ordinal'], values.sum(axis=0)))
                            by_event = res[name + '_by_event']
                            for eid, value in zip(out.eids, values):
                                by_event[eid][l] += value
        with rsk_mon:
            result['aed'] = aed = numpy.zeros(len(ddic), param['aed_dt'])
            for i, ((aid, eid), dd) in enumerate(sorted(ddic.items())):
                aed[i] = (aid, eid, dd)
        yield result
    yield res

@base.calculators.add('scenario_damage')
class ScenarioDamageCalculator(base.RiskCalculator):
    """
    Scenario damage calculator
    """
    core_task = scenario_damage
    is_stochastic = True
    precalc = 'scenario'
    accept_precalc = ['scenario']

    def pre_execute(self):
        super().pre_execute()
        num_floats = floats_in(self.assetcol['number'])
        if num_floats:
            logging.warning(
                'The exposure contains %d non-integer asset numbers: '
                'using floating point damage distributions', num_floats)
        bad = self.assetcol['number'] > 2**32 - 1
        for ass in self.assetcol[bad]:
            aref = self.assetcol.tagcol.id[ass['id']]
            logging.error("The asset %s has number=%s > 2^32-1!",
                          aref, ass['number'])
        self.param['approx_ddd'] = self.oqparam.approx_ddd or num_floats
        self.param['aed_dt'] = aed_dt = self.crmodel.aid_eid_dd_dt()
        self.param['master_seed'] = self.oqparam.master_seed
        A = len(self.assetcol)
        self.datastore.create_dset('dd_data/data', aed_dt, compression='gzip')
        self.datastore.create_dset('dd_data/indices', U32, (A, 2))
        self.riskinputs = self.build_riskinputs('gmf')
        self.start = 0

    def combine(self, acc, res):
        aed = res.pop('aed', ())
        if len(aed) == 0:
            return acc + res
        for aid, [(i1, i2)] in get_indices(aed['aid']).items():

            self.datastore['dd_data/indices'][aid] = (
                self.start + i1, self.start + i2)
        self.start += len(aed)
        hdf5.extend(self.datastore['dd_data/data'], aed)
        return acc + res

    def post_execute(self, result):
        """
        Compute stats for the aggregated distributions and save
        the results on the datastore.
        """
        if not result:
            self.collapsed()
            return
        dstates = self.crmodel.damage_states
        ltypes = self.crmodel.loss_types
        L = len(ltypes)
        R = self.R
        D = len(dstates)
        A = len(self.assetcol)
        indices = self.datastore['dd_data/indices'][()]
        if not len(self.datastore['dd_data/data']):
            logging.warning('There is no damage at all!')
        events_per_asset = (indices[:, 1] - indices[:, 0]).mean()
        logging.info('Found ~%d dmg distributions per asset', events_per_asset)

        # avg_ratio = ratio used when computing the averages
        oq = self.oqparam
        if oq.investigation_time:  # event_based_damage
            avg_ratio = oq.ses_ratio
        else:  # scenario_damage
            avg_ratio = 1. / oq.number_of_ground_motion_fields

        # damage by asset
        d_asset = numpy.zeros((A, R, L, D), F32)
        for (l, r, a, tot) in result['d_asset']:
            d_asset[a, r, l] = tot
        self.datastore['avg_damages-rlzs'] = d_asset * avg_ratio

        # damage by event: make sure the sum of the buildings is consistent
        tot = self.assetcol['number'].sum()
        dbe = numpy.zeros((self.E, L, D), U32)  # shape E, L, D
        dbe[:, :, 0] = tot
        for e, dmg_by_lt in result['d_event'].items():
            for l, dmg in enumerate(dmg_by_lt):
                dbe[e, l,  0] = tot - dmg.sum()
                dbe[e, l,  1:] = dmg
        self.datastore['dmg_by_event'] = dbe

        # consequence distributions
        del result['d_asset']
        del result['d_event']
        dtlist = [('event_id', U32), ('rlz_id', U16), ('loss', (F32, (L,)))]
        rlz = self.datastore['events']['rlz_id']
        for name, csq in result.items():
            if name.startswith('avg_'):
                c_asset = numpy.zeros((A, R, L), F32)
                for (l, r, a, stat) in result[name]:
                    c_asset[a, r, l] = stat
                self.datastore[name + '-rlzs'] = c_asset * avg_ratio
            elif name.endswith('_by_event'):
                arr = numpy.zeros(len(csq), dtlist)
                for i, (eid, loss) in enumerate(csq.items()):
                    arr[i] = (eid, rlz[eid], loss)
                self.datastore[name] = arr

@base.calculators.add('event_based_damage')
class EventBasedDamageCalculator(ScenarioDamageCalculator):
    """
    Event Based Damage calculator, able to compute avg_damages-rlzs,
    dmg_by_event and consequences.
    """
    core_task = scenario_damage
    precalc = 'event_based'
    accept_precalc = ['event_based', 'event_based_risk']
<|endoftext|>


<commit_before># -*- encoding: utf-8 -*-
###########################################################################
#    Copyright (c) 2013 OpenPyme - http://www.openpyme.mx/
#    All Rights Reserved.
#    Coded by: Agustín Cruz Lozano (agustin.cruz@openpyme.mx)
#
#    Permission is hereby granted, free of charge, to any person obtaining a copy
#    of this software and associated documentation files (the "Software"), to deal
#    in the Software without restriction, including without limitation the rights
#    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#    copies of the Software, and to permit persons to whom the Software is
#    furnished to do so, subject to the following conditions:
#
#    The above copyright notice and this permission notice shall be included in
#    all copies or substantial portions of the Software.
#
#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#    THE SOFTWARE.
#
##############################################################################

import os
import logging

from escpos import printer

logger = logging.getLogger(__name__)

class device:
    """ ESC/POS device object """

    def __init__(self):
        """Setup printer configuration

        """
        from proxypos.proxypos import config
        # Init printer
        ptype = config.get('printer.type').lower()
        settings = config.get('printer.settings')
        if ptype == 'usb':
            self.printer = printer.Usb(settings['idVendor'],
                                       settings['idProduct'])
        elif ptype == 'serial':
            self.printer = printer.Serial(settings['devfile'])
        elif ptype == 'network':
            self.printer = printer.Network(settings['host'])
        # Assign other default values
        self.printer.pxWidth = settings['pxWidth']
        # Set default widht with normal value
        self.printer.width = self.printer.widthA = settings['WidthA']
        self.printer.widthB = settings['WidthB']
        # Set correct table character
        if 'charSet' in settings:
            self.printer.text(settings['charSet'])

    def open_cashbox(self):
        self.printer.cashdraw(2)

    def print_receipt(self, receipt):
        """ Function used for print the recipt, currently is the only
        place where you can customize your printout.
        """
        path = os.path.dirname(__file__)

        filename = path + '/logo.jpg'

        self._printImgFromFile(filename)

        date = self._format_date(receipt['date'])
        self._bold(False)
        self._font('a')
        self._lineFeed(1)
        self._write(date, None, 'right')
        self._lineFeed(1)
        self._write(receipt['name'] + '\n', '', 'right')

        self._write(receipt['company']['name'] + '\n')
        self._write('RFC: ' + str(receipt['company']['company_registry']) + '\n')
        self._write(str(receipt['company']['contact_address']) + '\n')
        self._write('Telefono: ' + str(receipt['company']['phone']) + '\n')
        self._write('Cajero: ' + str(receipt['cashier']) + '\n')
        # self._write('Tienda: ' + receipt['store']['name'])
        self._lineFeed(1)
        for line in receipt['orderlines']:
            left = ' '.join([str(line['quantity']),
                             line['unit_name'],
                             line['product_name']
                            ]).encode('utf-8')
            right = self._decimal(line['price_with_tax'])
            self._write(left, right)
            self._lineFeed(1)

        self._lineFeed(2)
        self._write('Subtotal:', self._decimal(receipt['total_without_tax']) + '\n')
        self._write('IVA:', self._decimal(receipt['total_tax']) + '\n')
        self._write('Descuento:', self._decimal(receipt['total_discount']) + '\n')
        self._bold(True)
        self._font('b')
        self._write('TOTAL:', '$' + self._decimal(receipt['total_with_tax']) + '\n')

        # Set space for display payment methods
        self._lineFeed(1)
        self._font('a')
        self._bold(False)
        paymentlines = receipt['paymentlines']
        if paymentlines:
            for payment in paymentlines:
                self._write(payment['journal'], self._decimal(payment['amount']))

            self._bold(True)
            self._write('Cambio:', '$ ' + self._decimal(receipt['change']))
            self._bold(False)

        # Write customer data
        client = receipt['client']
        if client:
            self._lineFeed(4)
            self._write('Cliente: ' + client['name'].encode('utf-8'))
            self._lineFeed(1)
            self._write((u'Teléfono: ' + client['phone']).encode('utf-8'))
            self._lineFeed(1)
            self._write('Dirección: ' + client['contact_address'].encode('utf-8'))
            self._lineFeed(1)

        # Footer space
        self._write('GRACIAS POR SU COMPRA', '', 'center')

    # Helper functions to facilitate printing
    def _format_date(self, date):
        string = str(date['date']) + '/' + str(date['month']) + '/' + str(date['year']) + ' ' + str(date['hour']) + ':' + "%02d" % date['minute']
        return string

    def _write(self, string, rcolstr=None, align="left"):
        """Write simple text string. Remember \n for newline where applicable.
        rcolstr is a righthand column that may be added
        (e.g. a price on a receipt). Be aware that when rcolstr is
        used newline(s) may only be a part of rcolstr, and only as
        the last character(s)."""
        if align != "left" and len(string) < self.printer.width:
            blanks = 0
            if align == "right":
                blanks = self.printer.width - len(string.rstrip("\n"))
            if align == "center":
                blanks = (self.printer.width - len(string.rstrip("\n"))) / 2
            string = " " * blanks + string

        if not rcolstr:
            try:
                self.printer.text(string)
            except:
                logger.error('No pude escribir', exc_info=1)
                raise
        else:
            rcolStrRstripNewline = rcolstr.rstrip("\n")
            if "\n" in string or "\n" in rcolStrRstripNewline:
                raise ValueError("When using rcolstr in POSprinter.write only newline at the end of rcolstr is allowed and not in string (the main text string) it self.")
            # expand string
            lastLineLen = len(string) % self.printer.width + len(rcolStrRstripNewline)
            if lastLineLen > self.printer.width:
                numOfBlanks = (self.printer.width - lastLineLen) % self.printer.width
                string += numOfBlanks * " "
                lastLineLen = len(string) % self.printer.width + len(rcolStrRstripNewline)
            if lastLineLen < self.printer.width:
                numOfBlanks = self.printer.width - lastLineLen
                string += " " * numOfBlanks
            try:
                self.printer.text(string + rcolstr)
            except:
                logger.error('No pude escribir', exc_info=1)
                raise

    def _lineFeed(self, times=1, cut=False):
        """Write newlines and optional cut paper"""
        while times:
            try:
                self.printer.text("\n")
            except:
                raise
            times -= 1
        if cut:
            try:
                self.printer.cut('part')
            except:
                raise

    def lineFeedCut(self, times=6, cut=True):
        """Enough line feed for the cut to be beneath the previously printed text etc."""
        try:
            self._lineFeed(times, cut)
        except:
            raise

    def _font(self, font='a'):
        if font == 'a':
            self.printer._raw('\x1b\x4d\x01')
            self.printer.width = self.printer.widthA
        else:
            self.printer._raw('\x1b\x4d\x00')
            self.printer.width = self.printer.widthB

    def _bold(self, bold=True):
        if bold:
            self.printer._raw('\x1b\x45\x01')
        else:
            self.printer._raw('\x1b\x45\x00')

    def _decimal(self, number):
        return "%0.2f" % float(number)

    def _printImgFromFile(self, filename, resolution="high", align="center", scale=None):
        """Print an image from a file.
        resolution may be set to "high" or "low". Setting it to low makes the image a bit narrow (90x60dpi instead of 180x180 dpi) unless scale is also set.
        align may be set to "left", "center" or "right".
        scale resizes the image with that factor, where 1.0 is the full width of the paper."""
        try:
            from PIL import Image
            # Open file and convert to black/white (colour depth of 1 bit)
            img = Image.open(filename).convert("1")
            self._printImgFromPILObject(img, resolution, align, scale)
        except:
            raise

    def _printImgFromPILObject(self, imgObject, resolution="high", align="center", scale=None):
        """The object must be a Python ImageLibrary object, and the colordepth should be set to 1."""
        try:
            # If a scaling factor has been indicated
            if scale:
                assert type(scale) == float
                if scale > 1.0 or scale <= 0.0:
                    raise ValueError, "scale: Scaling factor must be larger than 0.0 and maximum 1.0"
                # Give a consistent output regardless of the resolution setting
                scale *= self.printer.pxWidth / float(imgObject.size[0])
                if resolution is "high":
                    scaleTuple = (scale * 2, scale * 2)
                else:
                    scaleTuple = (scale, scale * 2 / 3.0)
                # Convert to binary colour depth and resize
                imgObjectB = imgObject.resize([ int(scaleTuple[i] * imgObject.size[i]) for i in range(2) ]).convert("1")
            else:
                # Convert to binary colour depth
                imgObjectB = imgObject.convert("1")
            # Convert to a pixel access object
            imgMatrix = imgObjectB.load()
            width = imgObjectB.size[0]
            height = imgObjectB.size[1]
            # Print it
            self._printImgMatrix(imgMatrix, width, height, resolution, align)
        except:
            raise

    def _printImgMatrix(self, imgMatrix, width, height, resolution, align):
        """Print an image as a pixel access object with binary colour."""
        if resolution == "high":
            scaling = 24
            currentpxWidth = self.printer.pxWidth * 2
        else:
            scaling = 8
            currentpxWidth = self.printer.pxWidth
        if width > currentpxWidth:
            raise ValueError("Image too wide. Maximum width is configured to be " + str(currentpxWidth) + "pixels. The image is " + str(width) + " pixels wide.")
        tmp = ''
        for yScale in range(-(-height / scaling)):
            # Set mode to hex and 8-dot single density (60 dpi).
            if resolution == "high":
                outList = [ "0x1B", "0x2A", "0x21" ]
            else:
                outList = [ "0x1B", "0x2A", "0x00" ]
            # Add width to the communication to the printer. Depending on the alignment we count that in and add blank vertical lines to the outList
            if align == "left":
                blanks = 0
            if align == "center":
                blanks = (currentpxWidth - width) / 2
            if align == "right":
                blanks = currentpxWidth - width
            highByte = (width + blanks) / 256
            lowByte = (width + blanks) % 256
            outList.append(hex(lowByte))
            outList.append(hex(highByte))
            if resolution == "high":
                blanks *= 3
            if align == "left":
                pass
            if align == "center":
                for i in range(blanks):
                    outList.append(hex(0))
            if align == "right":
                for i in range(blanks):
                    outList.append(hex(0))
            for x in range(width):
                # Compute hex string for one vertical bar of 8 dots (zero padded from the bottom if necessary).
                binStr = ""
                for y in range(scaling):
                    # Indirect zero padding. Do not try to extract values from images beyond its size.
                    if (yScale * scaling + y) < height:
                        binStr += "0" if imgMatrix[x, yScale * scaling + y] == 255 else "1"
                    # Zero padding
                    else:
                        binStr += "0"
                outList.append(hex(int(binStr[0:8], 2)))
                if resolution == "high":
                    outList.append(hex(int(binStr[8:16], 2)))
                    outList.append(hex(int(binStr[16:24], 2)))
            for element in outList:
                try:
                    tmp += chr(int(element, 16))
                except:
                    raise

        self._write(tmp)
<commit_msg>Removed fixed logo path to home user folder<commit_after># -*- encoding: utf-8 -*-
###########################################################################
#    Copyright (c) 2013 OpenPyme - http://www.openpyme.mx/
#    All Rights Reserved.
#    Coded by: Agustín Cruz Lozano (agustin.cruz@openpyme.mx)
#
#    Permission is hereby granted, free of charge, to any person obtaining a copy
#    of this software and associated documentation files (the "Software"), to deal
#    in the Software without restriction, including without limitation the rights
#    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#    copies of the Software, and to permit persons to whom the Software is
#    furnished to do so, subject to the following conditions:
#
#    The above copyright notice and this permission notice shall be included in
#    all copies or substantial portions of the Software.
#
#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#    THE SOFTWARE.
#
##############################################################################

import os
import logging

from os.path import expanduser
from escpos import printer

logger = logging.getLogger(__name__)

class device:
    """ ESC/POS device object """

    def __init__(self):
        """Setup printer configuration

        """
        from proxypos.proxypos import config
        # Init printer
        ptype = config.get('printer.type').lower()
        settings = config.get('printer.settings')
        if ptype == 'usb':
            self.printer = printer.Usb(settings['idVendor'],
                                       settings['idProduct'])
        elif ptype == 'serial':
            self.printer = printer.Serial(settings['devfile'])
        elif ptype == 'network':
            self.printer = printer.Network(settings['host'])
        # Assign other default values
        self.printer.pxWidth = settings['pxWidth']
        # Set default widht with normal value
        self.printer.width = self.printer.widthA = settings['WidthA']
        self.printer.widthB = settings['WidthB']
        # Set correct table character
        if 'charSet' in settings:
            self.printer.text(settings['charSet'])

    def open_cashbox(self):
        self.printer.cashdraw(2)

    def print_receipt(self, receipt):
        """ Function used for print the recipt, currently is the only
        place where you can customize your printout.
        """
        filename = expanduser("~") + '/.proxypos/templates/logo.jpg'

        self._printImgFromFile(filename)

        date = self._format_date(receipt['date'])
        self._bold(False)
        self._font('a')
        self._lineFeed(1)
        self._write(date, None, 'right')
        self._lineFeed(1)
        self._write(receipt['name'] + '\n', '', 'right')

        self._write(receipt['company']['name'] + '\n')
        self._write('RFC: ' + str(receipt['company']['company_registry']) + '\n')
        self._write(str(receipt['company']['contact_address']) + '\n')
        self._write('Telefono: ' + str(receipt['company']['phone']) + '\n')
        self._write('Cajero: ' + str(receipt['cashier']) + '\n')
        # self._write('Tienda: ' + receipt['store']['name'])
        self._lineFeed(1)
        for line in receipt['orderlines']:
            left = ' '.join([str(line['quantity']),
                             line['unit_name'],
                             line['product_name']
                            ]).encode('utf-8')
            right = self._decimal(line['price_with_tax'])
            self._write(left, right)
            self._lineFeed(1)

        self._lineFeed(2)
        self._write('Subtotal:', self._decimal(receipt['total_without_tax']) + '\n')
        self._write('IVA:', self._decimal(receipt['total_tax']) + '\n')
        self._write('Descuento:', self._decimal(receipt['total_discount']) + '\n')
        self._bold(True)
        self._font('b')
        self._write('TOTAL:', '$' + self._decimal(receipt['total_with_tax']) + '\n')

        # Set space for display payment methods
        self._lineFeed(1)
        self._font('a')
        self._bold(False)
        paymentlines = receipt['paymentlines']
        if paymentlines:
            for payment in paymentlines:
                self._write(payment['journal'], self._decimal(payment['amount']))

            self._bold(True)
            self._write('Cambio:', '$ ' + self._decimal(receipt['change']))
            self._bold(False)

        # Write customer data
        client = receipt['client']
        if client:
            self._lineFeed(4)
            self._write('Cliente: ' + client['name'].encode('utf-8'))
            self._lineFeed(1)
            self._write((u'Teléfono: ' + client['phone']).encode('utf-8'))
            self._lineFeed(1)
            self._write('Dirección: ' + client['contact_address'].encode('utf-8'))
            self._lineFeed(1)

        # Footer space
        self._write('GRACIAS POR SU COMPRA', '', 'center')

    # Helper functions to facilitate printing
    def _format_date(self, date):
        string = str(date['date']) + '/' + str(date['month']) + '/' + str(date['year']) + ' ' + str(date['hour']) + ':' + "%02d" % date['minute']
        return string

    def _write(self, string, rcolstr=None, align="left"):
        """Write simple text string. Remember \n for newline where applicable.
        rcolstr is a righthand column that may be added
        (e.g. a price on a receipt). Be aware that when rcolstr is
        used newline(s) may only be a part of rcolstr, and only as
        the last character(s)."""
        if align != "left" and len(string) < self.printer.width:
            blanks = 0
            if align == "right":
                blanks = self.printer.width - len(string.rstrip("\n"))
            if align == "center":
                blanks = (self.printer.width - len(string.rstrip("\n"))) / 2
            string = " " * blanks + string

        if not rcolstr:
            try:
                self.printer.text(string)
            except:
                logger.error('No pude escribir', exc_info=1)
                raise
        else:
            rcolStrRstripNewline = rcolstr.rstrip("\n")
            if "\n" in string or "\n" in rcolStrRstripNewline:
                raise ValueError("When using rcolstr in POSprinter.write only newline at the end of rcolstr is allowed and not in string (the main text string) it self.")
            # expand string
            lastLineLen = len(string) % self.printer.width + len(rcolStrRstripNewline)
            if lastLineLen > self.printer.width:
                numOfBlanks = (self.printer.width - lastLineLen) % self.printer.width
                string += numOfBlanks * " "
                lastLineLen = len(string) % self.printer.width + len(rcolStrRstripNewline)
            if lastLineLen < self.printer.width:
                numOfBlanks = self.printer.width - lastLineLen
                string += " " * numOfBlanks
            try:
                self.printer.text(string + rcolstr)
            except:
                logger.error('No pude escribir', exc_info=1)
                raise

    def _lineFeed(self, times=1, cut=False):
        """Write newlines and optional cut paper"""
        while times:
            try:
                self.printer.text("\n")
            except:
                raise
            times -= 1
        if cut:
            try:
                self.printer.cut('part')
            except:
                raise

    def lineFeedCut(self, times=6, cut=True):
        """Enough line feed for the cut to be beneath the previously printed text etc."""
        try:
            self._lineFeed(times, cut)
        except:
            raise

    def _font(self, font='a'):
        if font == 'a':
            self.printer._raw('\x1b\x4d\x01')
            self.printer.width = self.printer.widthA
        else:
            self.printer._raw('\x1b\x4d\x00')
            self.printer.width = self.printer.widthB

    def _bold(self, bold=True):
        if bold:
            self.printer._raw('\x1b\x45\x01')
        else:
            self.printer._raw('\x1b\x45\x00')

    def _decimal(self, number):
        return "%0.2f" % float(number)

    def _printImgFromFile(self, filename, resolution="high", align="center", scale=None):
        """Print an image from a file.
        resolution may be set to "high" or "low". Setting it to low makes the image a bit narrow (90x60dpi instead of 180x180 dpi) unless scale is also set.
        align may be set to "left", "center" or "right".
        scale resizes the image with that factor, where 1.0 is the full width of the paper."""
        try:
            from PIL import Image
            # Open file and convert to black/white (colour depth of 1 bit)
            img = Image.open(filename).convert("1")
            self._printImgFromPILObject(img, resolution, align, scale)
        except:
            raise

    def _printImgFromPILObject(self, imgObject, resolution="high", align="center", scale=None):
        """The object must be a Python ImageLibrary object, and the colordepth should be set to 1."""
        try:
            # If a scaling factor has been indicated
            if scale:
                assert type(scale) == float
                if scale > 1.0 or scale <= 0.0:
                    raise ValueError, "scale: Scaling factor must be larger than 0.0 and maximum 1.0"
                # Give a consistent output regardless of the resolution setting
                scale *= self.printer.pxWidth / float(imgObject.size[0])
                if resolution is "high":
                    scaleTuple = (scale * 2, scale * 2)
                else:
                    scaleTuple = (scale, scale * 2 / 3.0)
                # Convert to binary colour depth and resize
                imgObjectB = imgObject.resize([ int(scaleTuple[i] * imgObject.size[i]) for i in range(2) ]).convert("1")
            else:
                # Convert to binary colour depth
                imgObjectB = imgObject.convert("1")
            # Convert to a pixel access object
            imgMatrix = imgObjectB.load()
            width = imgObjectB.size[0]
            height = imgObjectB.size[1]
            # Print it
            self._printImgMatrix(imgMatrix, width, height, resolution, align)
        except:
            raise

    def _printImgMatrix(self, imgMatrix, width, height, resolution, align):
        """Print an image as a pixel access object with binary colour."""
        if resolution == "high":
            scaling = 24
            currentpxWidth = self.printer.pxWidth * 2
        else:
            scaling = 8
            currentpxWidth = self.printer.pxWidth
        if width > currentpxWidth:
            raise ValueError("Image too wide. Maximum width is configured to be " + str(currentpxWidth) + "pixels. The image is " + str(width) + " pixels wide.")
        tmp = ''
        for yScale in range(-(-height / scaling)):
            # Set mode to hex and 8-dot single density (60 dpi).
            if resolution == "high":
                outList = [ "0x1B", "0x2A", "0x21" ]
            else:
                outList = [ "0x1B", "0x2A", "0x00" ]
            # Add width to the communication to the printer. Depending on the alignment we count that in and add blank vertical lines to the outList
            if align == "left":
                blanks = 0
            if align == "center":
                blanks = (currentpxWidth - width) / 2
            if align == "right":
                blanks = currentpxWidth - width
            highByte = (width + blanks) / 256
            lowByte = (width + blanks) % 256
            outList.append(hex(lowByte))
            outList.append(hex(highByte))
            if resolution == "high":
                blanks *= 3
            if align == "left":
                pass
            if align == "center":
                for i in range(blanks):
                    outList.append(hex(0))
            if align == "right":
                for i in range(blanks):
                    outList.append(hex(0))
            for x in range(width):
                # Compute hex string for one vertical bar of 8 dots (zero padded from the bottom if necessary).
                binStr = ""
                for y in range(scaling):
                    # Indirect zero padding. Do not try to extract values from images beyond its size.
                    if (yScale * scaling + y) < height:
                        binStr += "0" if imgMatrix[x, yScale * scaling + y] == 255 else "1"
                    # Zero padding
                    else:
                        binStr += "0"
                outList.append(hex(int(binStr[0:8], 2)))
                if resolution == "high":
                    outList.append(hex(int(binStr[8:16], 2)))
                    outList.append(hex(int(binStr[16:24], 2)))
            for element in outList:
                try:
                    tmp += chr(int(element, 16))
                except:
                    raise

        self._write(tmp)
<|endoftext|>


<commit_before>import numpy as np
import warnings
import GPy

def get_slices(input_list):
    num_outputs = len(input_list)
    _s = [0] + [ _x.shape[0] for _x in input_list ]
    _s = np.cumsum(_s)
    slices = [slice(a,b) for a,b in zip(_s[:-1],_s[1:])]
    return slices

def build_XY(input_list,output_list=None,index=None):
    num_outputs = len(input_list)
    if output_list is not None:
        assert num_outputs == len(output_list)
        Y = np.vstack(output_list)
    else:
        Y = None

    if index is not None:
        assert len(index) == num_outputs
        I = np.hstack( [np.repeat(j,_x.shape[0]) for _x,j in zip(input_list,index)] )
    else:
        I = np.hstack( [np.repeat(j,_x.shape[0]) for _x,j in zip(input_list,range(num_outputs))] )

    X = np.vstack(input_list)
    X = np.hstack([X,I[:,None]])

    return X,Y,I[:,None]#slices

def build_likelihood(Y_list,noise_index,likelihoods_list=None):
    Ny = len(Y_list)
    if likelihoods_list is None:
       likelihoods_list = [GPy.likelihoods.Gaussian(name="Gaussian_noise_%s" %j) for y,j in zip(Y_list,range(Ny))]
    else:
        assert len(likelihoods_list) == Ny
    #likelihood = GPy.likelihoods.mixed_noise.MixedNoise(likelihoods_list=likelihoods_list, noise_index=noise_index)
    likelihood = GPy.likelihoods.mixed_noise.MixedNoise(likelihoods_list=likelihoods_list)
    return likelihood

def ICM(input_dim, num_outputs, kernel, W_rank=1,W=None,kappa=None,name='ICM'):
    """
    Builds a kernel for an Intrinsic Coregionalization Model

    :input_dim: Input dimensionality (does not include dimension of indices)
    :num_outputs: Number of outputs
    :param kernel: kernel that will be multiplied by the coregionalize kernel (matrix B).
    :type kernel: a GPy kernel
    :param W_rank: number tuples of the corregionalization parameters 'W'
    :type W_rank: integer
    """
    if kernel.input_dim <> input_dim:
        kernel.input_dim = input_dim
        warnings.warn("kernel's input dimension overwritten to fit input_dim parameter.")

    K = kernel.prod(GPy.kern.Coregionalize(1, num_outputs, active_dims=[input_dim], rank=W_rank,W=W,kappa=kappa,name='B'),name=name)
    return K

def LCM(input_dim, num_outputs, kernels_list, W_rank=1,name='ICM'):
    """
    Builds a kernel for an Linear Coregionalization Model

    :input_dim: Input dimensionality (does not include dimension of indices)
    :num_outputs: Number of outputs
    :param kernel: kernel that will be multiplied by the coregionalize kernel (matrix B).
    :type kernel: a GPy kernel
    :param W_rank: number tuples of the corregionalization parameters 'W'
    :type W_rank: integer
    """
    Nk = len(kernels_list)
    K = ICM(input_dim,num_outputs,kernels_list[0],W_rank,name='%s%s' %(name,0))
    j = 1
    for kernel in kernels_list[1:]:
        K += ICM(input_dim,num_outputs,kernel,W_rank,name='%s%s' %(name,j))
        j += 1
    return K

def Private(input_dim, num_outputs, kernel, output, kappa=None,name='X'):
    """
    Builds a kernel for an Intrinsic Coregionalization Model

    :input_dim: Input dimensionality
    :num_outputs: Number of outputs
    :param kernel: kernel that will be multiplied by the coregionalize kernel (matrix B).
    :type kernel: a GPy kernel
    :param W_rank: number tuples of the corregionalization parameters 'W'
    :type W_rank: integer
    """
    K = ICM(input_dim,num_outputs,kernel,W_rank=1,kappa=kappa,name=name)
    K.B.W.fix(0)
    _range = range(num_outputs)
    _range.pop(output)
    for j in _range:
        K.B.kappa[j] = 0
        K.B.kappa[j].fix()
    return K
<commit_msg>Changed <> to != for Py3 compatibility<commit_after>import numpy as np
import warnings
import GPy

def get_slices(input_list):
    num_outputs = len(input_list)
    _s = [0] + [ _x.shape[0] for _x in input_list ]
    _s = np.cumsum(_s)
    slices = [slice(a,b) for a,b in zip(_s[:-1],_s[1:])]
    return slices

def build_XY(input_list,output_list=None,index=None):
    num_outputs = len(input_list)
    if output_list is not None:
        assert num_outputs == len(output_list)
        Y = np.vstack(output_list)
    else:
        Y = None

    if index is not None:
        assert len(index) == num_outputs
        I = np.hstack( [np.repeat(j,_x.shape[0]) for _x,j in zip(input_list,index)] )
    else:
        I = np.hstack( [np.repeat(j,_x.shape[0]) for _x,j in zip(input_list,range(num_outputs))] )

    X = np.vstack(input_list)
    X = np.hstack([X,I[:,None]])

    return X,Y,I[:,None]#slices

def build_likelihood(Y_list,noise_index,likelihoods_list=None):
    Ny = len(Y_list)
    if likelihoods_list is None:
       likelihoods_list = [GPy.likelihoods.Gaussian(name="Gaussian_noise_%s" %j) for y,j in zip(Y_list,range(Ny))]
    else:
        assert len(likelihoods_list) == Ny
    #likelihood = GPy.likelihoods.mixed_noise.MixedNoise(likelihoods_list=likelihoods_list, noise_index=noise_index)
    likelihood = GPy.likelihoods.mixed_noise.MixedNoise(likelihoods_list=likelihoods_list)
    return likelihood

def ICM(input_dim, num_outputs, kernel, W_rank=1,W=None,kappa=None,name='ICM'):
    """
    Builds a kernel for an Intrinsic Coregionalization Model

    :input_dim: Input dimensionality (does not include dimension of indices)
    :num_outputs: Number of outputs
    :param kernel: kernel that will be multiplied by the coregionalize kernel (matrix B).
    :type kernel: a GPy kernel
    :param W_rank: number tuples of the corregionalization parameters 'W'
    :type W_rank: integer
    """
    if kernel.input_dim != input_dim:
        kernel.input_dim = input_dim
        warnings.warn("kernel's input dimension overwritten to fit input_dim parameter.")

    K = kernel.prod(GPy.kern.Coregionalize(1, num_outputs, active_dims=[input_dim], rank=W_rank,W=W,kappa=kappa,name='B'),name=name)
    return K

def LCM(input_dim, num_outputs, kernels_list, W_rank=1,name='ICM'):
    """
    Builds a kernel for an Linear Coregionalization Model

    :input_dim: Input dimensionality (does not include dimension of indices)
    :num_outputs: Number of outputs
    :param kernel: kernel that will be multiplied by the coregionalize kernel (matrix B).
    :type kernel: a GPy kernel
    :param W_rank: number tuples of the corregionalization parameters 'W'
    :type W_rank: integer
    """
    Nk = len(kernels_list)
    K = ICM(input_dim,num_outputs,kernels_list[0],W_rank,name='%s%s' %(name,0))
    j = 1
    for kernel in kernels_list[1:]:
        K += ICM(input_dim,num_outputs,kernel,W_rank,name='%s%s' %(name,j))
        j += 1
    return K

def Private(input_dim, num_outputs, kernel, output, kappa=None,name='X'):
    """
    Builds a kernel for an Intrinsic Coregionalization Model

    :input_dim: Input dimensionality
    :num_outputs: Number of outputs
    :param kernel: kernel that will be multiplied by the coregionalize kernel (matrix B).
    :type kernel: a GPy kernel
    :param W_rank: number tuples of the corregionalization parameters 'W'
    :type W_rank: integer
    """
    K = ICM(input_dim,num_outputs,kernel,W_rank=1,kappa=kappa,name=name)
    K.B.W.fix(0)
    _range = range(num_outputs)
    _range.pop(output)
    for j in _range:
        K.B.kappa[j] = 0
        K.B.kappa[j].fix()
    return K
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2014-2018 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

import unittest
import mock
from openquake.baselib.general import gettemp
from openquake.baselib import hdf5
from openquake.calculators import base

class FakeParams(hdf5.LiteralAttrs):
    export_dir = '/tmp'
    hazard_calculation_id = None
    inputs = {'job_ini': gettemp('fake_job.ini')}
    concurrent_tasks = 0

    def to_params(self):
        return {}

class ErrorCalculator(base.BaseCalculator):
    def pre_execute(self):
        pass

    def execute(self):
        1 / 0

    def post_execute(self):
        pass

class BaseCalculatorTestCase(unittest.TestCase):
    def test_critical(self):
        calc = ErrorCalculator(FakeParams())
        with mock.patch('logging.error') as error, mock.patch(
                'logging.critical') as critical:
            self.assertRaises(ZeroDivisionError, calc.run)
        self.assertEqual(error.call_count, 0)
        self.assertEqual(critical.call_count, 1)

class CheckHazardRiskConsistencyTestCase(unittest.TestCase):
    def test_ok(self):
        base.check_precalc_consistency('scenario_risk', 'scenario')

    def test_inconsistent_mode(self):
        with self.assertRaises(base.InvalidCalculationID) as ctx:
            base.check_precalc_consistency('classical_risk', 'scenario')
        msg = str(ctx.exception)
        self.assertEqual(
            msg, "In order to run a risk calculation of kind "
            "'classical_risk', you need to provide a "
            "calculation of kind ['classical'], "
            "but you provided a 'scenario' instead")
<commit_msg>Fixed test [skip hazardlib]<commit_after># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2014-2018 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

import unittest
import mock
from openquake.baselib.general import gettemp
from openquake.baselib import hdf5
from openquake.calculators import base

class FakeParams(hdf5.LiteralAttrs):
    export_dir = '/tmp'
    hazard_calculation_id = None
    inputs = {'job_ini': gettemp('fake_job.ini')}
    concurrent_tasks = 0
    minimum_magnitude = 0

    def to_params(self):
        return {}

class ErrorCalculator(base.BaseCalculator):
    def pre_execute(self):
        pass

    def execute(self):
        1 / 0

    def post_execute(self):
        pass

class BaseCalculatorTestCase(unittest.TestCase):
    def test_critical(self):
        calc = ErrorCalculator(FakeParams())
        with mock.patch('logging.error') as error, mock.patch(
                'logging.critical') as critical:
            self.assertRaises(ZeroDivisionError, calc.run)
        self.assertEqual(error.call_count, 0)
        self.assertEqual(critical.call_count, 1)

class CheckHazardRiskConsistencyTestCase(unittest.TestCase):
    def test_ok(self):
        base.check_precalc_consistency('scenario_risk', 'scenario')

    def test_inconsistent_mode(self):
        with self.assertRaises(base.InvalidCalculationID) as ctx:
            base.check_precalc_consistency('classical_risk', 'scenario')
        msg = str(ctx.exception)
        self.assertEqual(
            msg, "In order to run a risk calculation of kind "
            "'classical_risk', you need to provide a "
            "calculation of kind ['classical'], "
            "but you provided a 'scenario' instead")
<|endoftext|>