compression-to-string (ad hoc implementation is written in C++ with the help of my cp.inc library, unfortunately).master
parent
9eb177bc27
commit
428b481b87
3 changed files with 313 additions and 0 deletions
@ -0,0 +1,6 @@ |
|||||||
|
# $Id: Makefile,v 1.1 2010-10-19 20:19:25 wirawan Exp $
|
||||||
|
# Created: 20101007
|
||||||
|
# Wirawan Purwanto
|
||||||
|
|
||||||
|
compress_errorbars.exe: compress_errorbars.cc |
||||||
|
$(CXX) -O2 -o $@ $^ -I$(WORK_DIR)
|
@ -0,0 +1,56 @@ |
|||||||
|
/*
|
||||||
|
|
||||||
|
$Id: compress_errorbar.cc,v 1.1 2010-10-19 20:19:25 wirawan Exp $ |
||||||
|
|
||||||
|
Date: 20101007 |
||||||
|
Wirawan Purwanto |
||||||
|
|
||||||
|
Small C++ utility to quickly compress an errorbar. |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstdio> |
||||||
|
|
||||||
|
#include "cp.inc/pcharlib.cpp" |
||||||
|
#include "cp.inc/pcharconv.cpp" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
int main(int argc, char *argv[], char *env[]) |
||||||
|
{ |
||||||
|
double v, e; |
||||||
|
int errdigits = 2; |
||||||
|
|
||||||
|
if (argc < 3) |
||||||
|
{ |
||||||
|
fprintf(stderr, "Minimum of two arguments required.\n"); |
||||||
|
return 2; |
||||||
|
} |
||||||
|
|
||||||
|
if (EOF == sscanf(argv[1], "%lg", &v)) |
||||||
|
{ |
||||||
|
fprintf(stderr, "Invalid value: %s", argv[1]); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
if (EOF == sscanf(argv[2], "%lg", &e)) |
||||||
|
{ |
||||||
|
fprintf(stderr, "Invalid errorbar: %s", argv[2]); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
if (argc > 3) |
||||||
|
{ |
||||||
|
if (EOF == sscanf(argv[3], "%d", &errdigits)) |
||||||
|
{ |
||||||
|
fprintf(stderr, "Invalid errorbar digits: %s", argv[3]); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
avgtostr compress_errorbar; |
||||||
|
fputs(+compress_errorbar(v, e, errdigits), stdout); |
||||||
|
fputs("\n", stdout); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,251 @@ |
|||||||
|
# $Id: errorbar.py,v 1.1 2010-10-19 20:19:25 wirawan Exp $ |
||||||
|
# |
||||||
|
# Module wpylib.math.stats.errorbar |
||||||
|
# Errorbar text handling for Python |
||||||
|
# |
||||||
|
# Created: 20081118 |
||||||
|
# Wirawan Purwanto |
||||||
|
# |
||||||
|
# Moved to wpylib, 20101007 |
||||||
|
|
||||||
|
import math |
||||||
|
from math import sqrt |
||||||
|
import os |
||||||
|
import os.path |
||||||
|
import re |
||||||
|
import wpylib.shell_tools as sh |
||||||
|
|
||||||
|
class regexp__aux(object): |
||||||
|
'''Auxiliary objects for routines below. Compiled into regexp objects |
||||||
|
for speed.''' |
||||||
|
# CHECKME FIXME: This class is NOT originally designed to be multithread-safe |
||||||
|
# since regex objects are shared between all threads. |
||||||
|
# To be thread-safe, the regex objects or aux classes may need to be |
||||||
|
# instantiated separately for each thread instance, or whatever else way |
||||||
|
# possible. |
||||||
|
init = False |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def initialize(): |
||||||
|
R = regexp__aux |
||||||
|
# Each of the regex stuff below is a 2-tuple containing the regex |
||||||
|
# object and its associated function to extract the value/errbar pair |
||||||
|
# (as a tuple) from the re.Match object. |
||||||
|
# |
||||||
|
# Standard errorbar matcher (errorbar in parantheses), in the form of a tuple |
||||||
|
# The first element of the matcher is a regexp matcher object, and the |
||||||
|
# second element is a function to extract the (value,error) tuple from the |
||||||
|
# successful matching process. |
||||||
|
R.errbar = \ |
||||||
|
( |
||||||
|
re.compile(r"([-+]?\d+)" # leading digits with optional sign |
||||||
|
r"((?:\.\d*)?)" # optional digits after decimal point |
||||||
|
r"\((\d+\.?\d*[Ee]?[-+]?\d*)\)" # errorbar in paranthesis |
||||||
|
r"((?:[Ee][-+]?\d+)?)$"), |
||||||
|
lambda M: ( float(M.group(1) + M.group(2) + M.group(4)), # value = simple composition |
||||||
|
# for errorbar: |
||||||
|
# - must multiply with value's exponent |
||||||
|
# - and account for decimal digits (if any) |
||||||
|
float(M.group(3)) * float("1"+M.group(4)) * 10**(-max(len(M.group(2))-1, 0)) ) |
||||||
|
) |
||||||
|
# Later additional matchers can be added here |
||||||
|
R.init = True |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def aux(): |
||||||
|
R = regexp__aux |
||||||
|
if not R.init: R.initialize() |
||||||
|
return R |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def match(matcher, Str, rslt, flatten=False): |
||||||
|
'''Matches the string `Str' against the errorbar regexp pattern in `matcher[0]'. |
||||||
|
If it matches, the value and error are added to the `rslt' list. |
||||||
|
Depending on whether `flatten' is True or not, it is added as a tuple or as |
||||||
|
two elements, respectively, into the `rslt' list.''' |
||||||
|
# Note: matcher is an object like R.errbar above. |
||||||
|
m = matcher[0].match(Str) |
||||||
|
if m: |
||||||
|
if flatten: |
||||||
|
rslt.extend(matcher[1](m)) |
||||||
|
else: |
||||||
|
rslt.append(matcher[1](m)) |
||||||
|
return True |
||||||
|
else: |
||||||
|
return False |
||||||
|
|
||||||
|
def expand(obj, convert_float=False, flatten=False): |
||||||
|
'''Expands compressed errorbar notation to two consecutive numbers |
||||||
|
(returned as a tuple). |
||||||
|
|
||||||
|
Input: The input can be a string, or a list of strings. |
||||||
|
|
||||||
|
Output: |
||||||
|
The list element that has the format of "VAL(ERR)" (or its scientific |
||||||
|
format twist) will be expanded into two numbers in the output list. |
||||||
|
All other elements will be passed "as is" to the output. |
||||||
|
Optionally, the non-float items can be force-converted to floats if |
||||||
|
the convert_float is set to True. |
||||||
|
''' |
||||||
|
if getattr(obj, "__iter__", False): |
||||||
|
iterable_inp = True |
||||||
|
objs = obj |
||||||
|
else: |
||||||
|
iterable_inp = False |
||||||
|
objs = ( obj, ) |
||||||
|
|
||||||
|
rgx = regexp__aux.aux() |
||||||
|
|
||||||
|
rslt = [] |
||||||
|
for o in objs: |
||||||
|
t = type(o) |
||||||
|
if t == int or t == float or t == complex: |
||||||
|
rslt.append(o) |
||||||
|
else: |
||||||
|
# Assume a string! |
||||||
|
o = o.strip() |
||||||
|
#m = rgx.errbar.match(o) |
||||||
|
if (rgx.match(rgx.errbar, o, rslt, flatten)): |
||||||
|
#print "match: errbar" |
||||||
|
pass |
||||||
|
elif convert_float: |
||||||
|
# Convert to float right away, store into the `rslt' list |
||||||
|
rslt.append(float(o)) |
||||||
|
#rslt.append( (float(o),) ) |
||||||
|
else: |
||||||
|
# Unless otherwise requested, the object will not be converted |
||||||
|
# to float: |
||||||
|
rslt.append(o) |
||||||
|
return rslt |
||||||
|
|
||||||
|
|
||||||
|
COMPRESS_ERRORBAR_EXE = os.path.dirname(__file__) + "/compress_errorbar.exe" |
||||||
|
|
||||||
|
def compress_errorbar_cxx(v, e, errdigits=2): |
||||||
|
"""Temporary plug-hole measure to get python compress errorbars. |
||||||
|
Using a small C++ executable to perform the task.""" |
||||||
|
#perl_lib_dir = sh.getenv("WORK_DIR", "HOME") + "/scripts" |
||||||
|
return sh.pipe_out((COMPRESS_ERRORBAR_EXE, str(v), str(e), str(errdigits))).strip() |
||||||
|
|
||||||
|
|
||||||
|
class float_decomp(object): |
||||||
|
"""Floating-point decomposer. |
||||||
|
We are assuming IEEE double precision here.""" |
||||||
|
|
||||||
|
def __init__(self, val, decdigits=16): |
||||||
|
self.val = val |
||||||
|
V = "%+.*e" % (decdigits, val) |
||||||
|
self.sign = V[0] |
||||||
|
self.digits = V[1] + V[3:3+16] |
||||||
|
self.exp = int(V[20:]) |
||||||
|
|
||||||
|
set = __init__ |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class errorbar(object): |
||||||
|
"""A simple class holding a scalar value with an error bar. |
||||||
|
When converted to a float, its mean is returned. |
||||||
|
When converted to a string, its string representation is returned. |
||||||
|
which usually is meant to be a value with errorbar in parenthesis. |
||||||
|
This value is custom-made.""" |
||||||
|
|
||||||
|
ebproc = staticmethod(compress_errorbar_cxx) |
||||||
|
|
||||||
|
def __init__(self, val, err, eb=None, ebproc=None): |
||||||
|
self.val = val |
||||||
|
self.err = err |
||||||
|
if ebproc != None: |
||||||
|
self.ebproc = ebproc |
||||||
|
if eb == None: |
||||||
|
self.eb = self.ebproc(val, err) |
||||||
|
else: |
||||||
|
self.eb = eb |
||||||
|
|
||||||
|
def __float__(self): |
||||||
|
return self.val |
||||||
|
def __float__(self): |
||||||
|
return self.val |
||||||
|
value = __float__ |
||||||
|
mean = __float__ |
||||||
|
def error(self): |
||||||
|
return self.err |
||||||
|
def __str__(self): |
||||||
|
if getattr(self, "eb", None): |
||||||
|
return self.eb |
||||||
|
else: |
||||||
|
return "%g +- %g" % (self.val, self.err) |
||||||
|
display = __str__ |
||||||
|
def __repr__(self): |
||||||
|
return "errorbar(%s,%s,'%s')" % (self.val, self.err, self.display()) |
||||||
|
def ebupdate(self): |
||||||
|
self.eb = self.ebproc(self.val, self.err) |
||||||
|
def copy(self): |
||||||
|
return self.__class__(self.val, self.err, self.eb, self.ebproc) |
||||||
|
# Some algebraic operations with scalars are defined here: |
||||||
|
def __mul__(self, y): |
||||||
|
"""Scales y by a scalar value.""" |
||||||
|
return self.__class__(self.val*y, self.err*y, ebproc=self.ebproc) |
||||||
|
__rmul__ = __mul__ |
||||||
|
def __imul__(self, y): |
||||||
|
"""Scales itself by a scalar value.""" |
||||||
|
self.val *= y |
||||||
|
self.err *= y |
||||||
|
self.ebupdate() |
||||||
|
|
||||||
|
def __add__(self, y): |
||||||
|
"""Adds by a scalar value or another errorbar value. |
||||||
|
In the latter case, the uncertainty is assumed to be uncorrelated, |
||||||
|
thus we can use a simple formula to update the errorbar.""" |
||||||
|
if isinstance(y, errorbar): |
||||||
|
return self.__class__(self.val+y.val, |
||||||
|
sqrt(self.err**2 + y.err**2), |
||||||
|
ebproc=self.ebproc) |
||||||
|
else: |
||||||
|
return self.__class__(self.val+y, self.err, ebproc=self.ebproc) |
||||||
|
__radd__ = __add__ |
||||||
|
|
||||||
|
def __sub__(self, y): |
||||||
|
"""Subtracts by a scalar value or another errorbar value. |
||||||
|
In the latter case, the uncertainty is assumed to be uncorrelated, |
||||||
|
thus we can use a simple formula to update the errorbar.""" |
||||||
|
if isinstance(y, errorbar): |
||||||
|
return self.__class__(self.val-y.val, |
||||||
|
sqrt(self.err**2 + y.err**2), |
||||||
|
ebproc=self.ebproc) |
||||||
|
else: |
||||||
|
return self.__class__(self.val-y, self.err, ebproc=self.ebproc) |
||||||
|
|
||||||
|
def __rsub__(self, y): |
||||||
|
"""Subtracts this errorbar value from a scalar value or another |
||||||
|
errorbar value. |
||||||
|
In the latter case, the uncertainty is assumed to be uncorrelated, |
||||||
|
thus we can use a simple formula to update the errorbar.""" |
||||||
|
if isinstance(y, errorbar): |
||||||
|
return self.__class__(y.val-self.val, |
||||||
|
sqrt(self.err**2 + y.err**2), |
||||||
|
ebproc=self.ebproc) |
||||||
|
else: |
||||||
|
return self.__class__(y-self.val, self.err, ebproc=self.ebproc) |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def create_str(s): |
||||||
|
"""Creates an errorbar object from an errorbar string.""" |
||||||
|
eb = expand(s, convert_float=True)[0] |
||||||
|
return errorbar(*eb) |
||||||
|
|
||||||
|
|
||||||
|
class errorbar_compressor(object): |
||||||
|
"""Compressor for errorbar string.""" |
||||||
|
def __init__(self): |
||||||
|
self.errdigits = 2 |
||||||
|
def __call__(self, val, err, **args): |
||||||
|
errdigits = args.get("errdigits", self.errdigits) |
||||||
|
v = float_decomp(val) |
||||||
|
e = float_decomp(err, decdigits=errdigits) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#def errorbar_algebra |
||||||
|
|
||||||
|
|
Loading…
Reference in new issue