# Source code for ixpeobssim.utils.math_

```#!/usr/bin/env python
#
# Copyright (C) 2015, the ixpeobssim team.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

from __future__ import print_function, division

import numpy
import sys

[docs]
def fold_angle_deg(phi):
"""Fold an azimuthal angle (in degrees) within [-180, 180] to [-90, 90].

Parameters
----------
phi : array_like or scalar
The input angle or array of values.
"""
return phi +\
180. * numpy.logical_and(phi >= -180, phi < -90.) -\
180. * numpy.logical_and(phi > 90., phi <= 180.)

[docs]
"""Fold an azimuthal angle (in degrees) within [-pi, pi] to [-pi/2, pi/2].

Parameters
----------
phi : array_like or scalar
The input angle or array of values.
"""
return phi +\
numpy.pi * numpy.logical_and(phi >= -numpy.pi, phi < -0.5 * numpy.pi) -\
numpy.pi * numpy.logical_and(phi > 0.5 * numpy.pi, phi <= numpy.pi)

[docs]
def modulo_2pi(phi):
"""Compute the modulo operation bringing the output angles (in radians)
into the interval [-pi, pi].

Parameters
----------
phi : array_like or scalar
The input angle or array of values.
"""
return phi + 2 * numpy.pi * (phi < -numpy.pi) - 2 * numpy.pi * (phi > numpy.pi)

[docs]
def format_value(value, precision=3):
"""Format a number with a reasonable precision
"""
if isinstance(value, str):
return value
else:
fmt = '%%.%dg' % precision
return fmt % value

[docs]
def decimal_places(val):
"""Calculate the number of decimal places so that a given value is rounded
to exactly two signficant digits.

Note that we add epsilon to the argument of the logarithm in such a way
that, e.g., 0.001 is converted to 0.0010 and not 0.00100. For values greater
than 99 this number is negative.
"""
return 1 - int(numpy.log10(val + sys.float_info.epsilon)) + 1 * (val < 1.)

[docs]
def decimal_power(val):
"""Calculate the order of magnitude of a given value,i.e., the largest
power of ten smaller than the value.
"""
return int(numpy.log10(val + sys.float_info.epsilon)) - 1 * (val < 1.)

[docs]
def format_value_error(value, error, pm='+/-', max_dec_places=6):
"""Format a measurement with the proper number of significant digits.
"""
value = float(value)
error = float(error)
assert error >= 0
if error == 0:
return '%s %s 0' % (format_value(value), pm)
if error == numpy.inf:
return '%s %s inf' % (format_value(value), pm)
dec_places = decimal_places(error)
if dec_places >= 0 and dec_places <= max_dec_places:
fmt = '%%.%df %s %%.%df' % (dec_places, pm, dec_places)
else:
p = decimal_power(abs(value))
scale = 10 ** p
value /= scale
error /= scale
dec_places = decimal_places(error)
if dec_places > 0:
if p > 0:
exp = 'e+%02d' % p
else:
exp = 'e-%02d' % abs(p)
fmt = '%%.%df%s %s %%.%df%s' %\
(dec_places, exp, pm, dec_places, exp)
else:
fmt = '%%d %s %%d' % pm
return fmt % (value, error)

[docs]
def weighted_average(values, weights=None):
"""Return the weighted average of an array of values.

This is simply a wrapper over the numpy.average() function.
"""
return numpy.average(values, weights=weights)

```