Python Forum
Thread Rating:
  • 1 Vote(s) - 1 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Algrbra Package
#1
#I'm working on an algebra package for Python. It includes a LinearRelationship class where every instance is
#a function of the general form y = mx + b, and I'm planning on adding as many of the basic function forms as I
#can, e.g. polynomials as a general class. Here's the Numbers module so far:

# Module:           Numbers
# Date Started:     February 2, 2018
# Author:           George Keith Watson, a.k.a. Keith Michael Collins
# Copyrights:       (c) 2017 George Keith Watson, a.k.a. Keith Michael Collins
# Application:      Chemistry Laboratory, including Medical.
# Documentation:
#

import math

class Rational:

    registry    = {}

    def __init__(self, name, numerator, denominator):
        self.name           = None
        self.numerator      = None
        self.denominator    = None
        self.floatValue     = None
        if name != None and isinstance(name, str) and numerator != None and isinstance(numerator,int) and \
                denominator != None and isinstance(denominator, int):
            self.name               = name
            self.numerator          = numerator
            self.denominator        = denominator
            Rational.registry[name] = self

    def floatValue(self):
        return self.numerator / self.denominator

    def isInteger(self):
        if self.denominator == 1:
            return True
        self.floatValue  = self.floatValue()
        if self.floatValue - math.floor( self.floatValue ) == 0:
            return True
        return False

    def isWhole(self):
        if self.isInteger():
            self.floatValue = self.floatValue()
            return self.floatValue >= 0
        return False

    def isNatural(self):
        if self.isWhole():
           return self.floatValue >= 1
        return False

    def set(self, numerator=None, denominator=None):
        if numerator != None:
            self.numerator = numerator
        if denominator != None:
            self.denominator    = denominator
        return self

    def __str__(self):
        return str(self.numerator) + " / " + (str(self.denominator))

class Real:

    registry    = {}

    def __init__(self, name, value, sigDigits=None):
        self.name       = None
        self.value      = None
        self.sigDigits  = None
        if name != None and isinstance(name, str) and value != None and isinstance(value, float):
            self.name       = name
            self.value      = value
            Real.registry[name]   = self

    def set(self, value=None, sigDigits=None):
        if value != None and (isinstance(value,float) or isinstance(value,Real)):
            self.value  = value
        if sigDigits != None and isinstance(sigDigits,int) and sigDigits > 0:
            self.sigDigits  = sigDigits
        return self

    def __str__(self):
        return str(self.value)


class Imaginary:

    registry    = {}

    def __init__(self, name, coefficient):
        self.name           = None
        self.coefficient    = None
        if name != None and isinstance(name, str) and coefficient != None and \
                (isinstance(coefficient,int) or isinstance(coefficient, float) or isinstance(coefficient,Real)):
            self.name           = name
            self.coefficient    = coefficient
            Imaginary.registry[name] = self

    def set(self, coefficient):
        if coefficient != None and (isinstance(coefficient,int) or isinstance(coefficient, float) or isinstance(coefficient,Real)):
            self.coefficient    = coefficient
        return self

    def __str__(self):
        return str(self.coefficient) + "i"

class Complex:

    registry    = {}

    def __init__(self, name, x, y):
        "z = x + iy"
        self.x          = None
        self.y          = None
        self.name       = None

        if name != None and isinstance(name, str) and x != None and (isinstance(x, float) or isinstance(x,Real)) and \
                        y != None and (isinstance(y,float) or isinstance(y,Real)):
            self.name       = name
            self.x          = x
            self.y          = y
            Complex.registry[name]   = self

    def set(self, x=None,y=None):
        if x != None and (isinstance(x, float) or isinstance(x,Real)):
            self.x  = x
        if y != None and (isinstance(y, float) or isinstance(y,Real)):
            self.y  = y
        return self

    def __str__(self):
        return str(self.x) + " + " + str(self.y) + "i"
#2
Looks interesting, but I do wonder if you don't realize that complex numbers are already readily available:
>>> a = 5j
>>> a
5j
>>> b = 3 + 7j
>>> a * b
(-35+15j)
>>>
#3
OK, but my reason for making a separate class is to include methods that operate on complex numbers.
#4
I did some work this. The initial, posted yesterday, only took a couple of hours of work. Today was a full day.
Warning: this code has not been tested.

# Module:           Numbers
# Date Started:     February 2, 2018
# Author:           George Keith Watson, a.k.a. Keith Michael Collins
# Copyrights:       (c) 2017 George Keith Watson, a.k.a. Keith Michael Collins
# Application:      Chemistry Laboratory, including Medical.
# Documentation:
#   2018-02-03
#       One of the primary goals of this module is to be able to guarantee particular levels of accuracy of results
#       rather than trusting the float class of Python running on any machine's cpu to do everything without error.
#   2018-02-04
#       Plans:
#           add comparison and math operator overloading to Scientific.
#           complete significant digits qualification of values using Scientific as standard.
#           implement comparison and math operator overloading for different type operands.
#

import math

class Scientific:
    "A number expressed entirely in scientific notation accounting for precision, i.e. significant digits which is decimal places+1"
    registry    = {}
    def __init__(self, name, coefficient, exponent=1, decimalPlaces=None):
        "For standardization purposes, the coefficient is scaled to number whose absolute value is greater than 0 and less than 10.\nExponent is any integer."
        self.name           = None
        self.coefficient    = None
        self.exponent       = None
        self.decimalPlaces  = None
        self.qualifiedVal   = None
        if name != None and isinstance( name, str ):
            self.name   = name
            self.set( coefficient, exponent, decimalPlaces=None )
            Scientific.registry[name]   = self
        else:
            raise Exception("Scientific constructor - invalid name passed to constructor:\t" + str(name) )

    def set(self, coefficient, exponent=1, decimalPlaces=None ):
        if exponent == None:
            exponent = 1
        elif isinstance( exponent, int ):
            self.exponent   = exponent
        else:
            raise Exception("Scientific self.set() - invalid exponent, must be int:\t" + str(exponent))
        if coefficient != None and (isinstance(coefficient, float) or isinstance(coefficient, int)):
            if isinstance(coefficient,int):
                self.coefficient = float(coefficient)
            else:
                self.coefficient = coefficient
            while math.fabs(self.coefficient) >= 10:
                self.exponent += 1
                self.coefficient /= 10
                print(str(self.coefficient))
            while math.fabs(self.coefficient) < 1:
                self.exponent -= 1
                self.coefficient *= 10
            if decimalPlaces != None:
                if isinstance(decimalPlaces, int) and decimalPlaces > 0:
                    pass
                else:
                    raise Exception("Scientific self.set() - invalid decimalPlaces argument, must be positive integer:\t" + str( decimalPlaces ))
            else:
                self.decimalPlaces  = len(str(self.coefficient)) - 2

        elif isinstance(coefficient, Real):
            pass
        elif isinstance(coefficient, Rational):
            pass
        else:
            raise Exception("Scientific self.set() - invalid coefficient, must be float, int, Real, or Rational:\t" + str(coefficient))

    def qualifiedValue(self):
        "compute value qualified by sigDigits or decimalPlaces"
        if self.decimalPlaces != None:
            multiplier = math.pow(10, self.decimalPlaces)
            self.qualifiedVal = math.floor( self.coefficient * multiplier ) / multiplier
        else:
            self.qualifiedVal   = self.coefficient
        return self.qualifiedVal

    def __str__(self):
        string = str(self.coefficient)
        while len(string) < self.decimalPlaces + 2:
            string += "0"
        return string + " E" + str(self.exponent)


class Rational:

    registry    = {}

    def __init__(self, name, numerator, denominator, sigDigits=None, notQualified=False):
        "set() must be called to properly update the state of the object once it is constructed."
        self.name           = None
        self.numerator      = None
        self.denominator    = None
        self.sigDigits      = None
        self.notQualified   = None
        self.floatVal       = None
        self.qualifiedVal   = None
        if name != None and isinstance(name, str):
            self.name               = name
            self.set( numerator, denominator, sigDigits, notQualified )
            Rational.registry[name] = self
        else:
            raise Exception("Rational constructor - invalid name passed to constructor:\t" + str(name) )

    def set(self, numerator=None, denominator=None, sigDigits=None, notQualified=False):
        if numerator != None and isinstance(numerator, int):
            self.numerator = numerator
        if denominator != None:
            if isinstance(denominator, int) and denominator != 0:
                self.denominator = denominator
        else:
            self.denominator = 1
        if sigDigits != None and isinstance(sigDigits, int) and sigDigits > 0:
            self.sigDigits = sigDigits
        else:
            self.sigDigits = None
        if numerator != None and denominator != None:
            self.floatValue()
            self.notQualified = notQualified
            if not self.notQualified:
                self.qualifiedValue()
        return self

    def leastCommonMultiple(n, m):
        candidate = max(n, m)
        while True:
            if (candidate % n == 0) and (candidate % m == 0):
                break
            candidate += 1
        return candidate

    def floatValue(self):
        self.floatVal     = self.numerator / self.denominator
        return self.floatVal

    def qualifiedValue(self):
        "compute value qualified by sigDigits or decimalPlaces"
        if self.numerator != None and self.denominator != None:
            self.floatValue()
            if self.sigDigits != None:
                #   Use Scientific class to standardize: significant digits = decimal places + 1
                pass
            else:
                self.qualifiedVal   = self.floatVal
            return self.qualifiedVal
        return None

    def isInteger(self):
        if self.denominator == 1:
            return True
        return self.floatVal - math.floor(self.floatVal) == 0

    def isWhole(self):
        if self.isInteger():
            return self.floatVal >= 0
        return False

    def isNatural(self):
        if self.isWhole():
           return self.floatVal >= 1
        return False

    def __eq__(self, other):
        if isinstance(other, Rational):
            lcm = self.leastCommonMultiple( self.denominator, other.denominator )
            selfNumerator   = self.numerator * ( lcm / self.denominator )
            otherNumerator  = other.numerator * ( lcm / other.denominator )
            return selfNumerator == otherNumerator
        return None

    def __ne__(self, other):
        if isinstance(other, Rational):
            return not self.__eq__(other)
        return None

    def __gt__(self, other):
        if isinstance(other, Rational):
            lcm = self.leastCommonMultiple( self.denominator, other.denominator )
            selfNumerator   = self.numerator * ( lcm / self.denominator )
            otherNumerator  = other.numerator * ( lcm / other.denominator )
            return selfNumerator > otherNumerator
        return None

    def __lt__(self, other):
        if isinstance(other, Rational):
            lcm = self.leastCommonMultiple( self.denominator, other.denominator )
            selfNumerator   = self.numerator * ( lcm / self.denominator )
            otherNumerator  = other.numerator * ( lcm / other.denominator )
            return selfNumerator < otherNumerator
        return None

    def __ge__(self, other):
        if isinstance(other, Rational):
            return self.__gt__(other) or self.__eq__(other)
        return None

    def __le__(self, other):
        if isinstance(other, Rational):
            return self.__lt__(other) or self.__eq__(other)
        return None

    def __add__(self, other):
        "result is returned as a new Rational instance"
        if isinstance(other, Rational):
            lcm = self.leastCommonMultiple( self.denominator, other.denominator )
            selfNumerator   = self.numerator * ( lcm / self.denominator )
            otherNumerator  = other.numerator * ( lcm / other.denominator )
            return Rational("__add__result", selfNumerator + otherNumerator, lcm, self.sigDigits, self.notQualified)
        return None

    def __sub__(self, other):
        "result is returned as a new Rational instance"
        if isinstance(other, Rational):
            lcm = self.leastCommonMultiple( self.denominator, other.denominator )
            selfNumerator   = self.numerator * ( lcm / self.denominator )
            otherNumerator  = other.numerator * ( lcm / other.denominator )
            return Rational("__sub__result", selfNumerator - otherNumerator, lcm, self.sigDigits, self.notQualified)
        return None

    def __mul__(self, other):
        "result is returned as a new Rational instance"
        if isinstance(other, Rational):
            return Rational("__mul__result", self.numerator * other.numerator, self.denominator * other.denominator, self.sigDigits, self.notQualified)
        return None

    def __truediv__(self, other):
        "result is returned as a new Rational instance"
        if isinstance(other, Rational):
            #   check for divide by zero
            if other.numerator == 0:
                raise Exception("Rational number division - DIVIDE BY ZERO ATTEMPTED")
            lcm = self.leastCommonMultiple( self.denominator, other.denominator )
            selfNumerator   = self.numerator * ( lcm / self.denominator )
            otherNumerator  = other.numerator * ( lcm / other.denominator )
            return Rational("__truediv__result", round(selfNumerator / otherNumerator), lcm, self.sigDigits, self.notQualified)
        return None

    def __abs__(self):
        "result is returned as a new Rational instance without a name"
        if self.numerator > 0:
            if self.denominator > 0:
                return Rational("__abs__result", self.numerator, self.denominator)
            else:
                return Rational("__abs__result", self.numerator, -self.denominator)
        elif self.denominator > 0:
            return Rational("__abs__result", -self.numerator, self.denominator)
        return Rational("__abs__result", -self.numerator, -self.denominator)

    def __str__(self):
        return str(self.numerator) + " / " + (str(self.denominator))


class Real:

    registry    = {}

    #   sigDigits is always used if present and overrides decimalPlaces as the precision guarantee.
    #
    def __init__(self, name, value, sigDigits=None, notQualified=None):
        "set() must be called to properly update the state of the object once it is constructed."
        self.name           = None
        self.value          = None
        self.sigDigits      = None
        self.notQualified   = None
        #   where possible, store value qualified by sigDigits or decimalPlaces for convenience and efficiency
        self.qualifiedVal   = None
        if name != None and isinstance(name, str) and value != None and (isinstance(value, float) or isinstance(value, int)):
            self.name       = name
            self.set(value, sigDigits, notQualified)
            Real.registry[name]   = self

    #   qualified value is always used unless the user specifies that the raw value can be used.
    #   qualified means sigDigits or decimalPlaces is used.
    #
    def set(self, value=None, sigDigits=None, notQualified=False):
        if value != None and (isinstance(value,float) or isinstance(value,int)):
            self.value          = value
            self.qualifiedVal   = value
        if sigDigits != None and isinstance(sigDigits,int) and sigDigits > 0:
            self.sigDigits  = sigDigits
        else:
            self.sigDigits  = None
        self.notQualified   = notQualified
        if not self.notQualified:
            self.qualifiedValue()
        return self

    def qualifiedValue(self):
        "compute value qualified by sigDigits or decimalPlaces"
        if self.sigDigits != None:
            #   Use Scientific class to standardize: significant digits = decimal places + 1
            pass
        else:
            self.qualifiedVal   = self.value
        return self.qualifiedVal

    #   for operator overloading any type other than Real must be imported into this class, so a temporary Real is
    #   constructed.
    def _import(self, other, name=None):
        if isinstance(other,Real):
            return other
        if name == None:
            name = "no name"
        if isinstance(other,int) or isinstance(other,float):
            return Real(  name, other, self.sigDigits, self.notQualified )


    def __eq__(self, other):
        other = self._import(other)
        if self.notQualified:
            return self.floatVal == other.floatVal
        else:
            return self.qualifiedVal == other.qualifiedValue()

    def __ne__(self, other):
        other = self._import(other)
        return not self.__eq__(other)

    def __ge__(self, other):
        other = self._import(other)
        if self.__eq__(other):
            return True
        return self.__gt__(other)

    def __gt__(self, other):
        other = self._import(other)
        if self.notQualified:
            return self.value > other.value
        else:
            return self.qualifiedVal > other.qualifiedValue()

    def __le__(self, other):
        other = self._import(other)
        if self.__eq__(other):
            return True
        return self.__lt__(other)

    def __lt__(self, other):
        other = self._import(other)
        if self.notQualified:
            return self.value < other.value
        else:
            return self.qualifiedVal < other.qualifiedValue()

    def __add__(self, other):
        "result is returned as a new Real instance with name __add__result"
        other = self._import(other)
        if self.notQualified:
            return Real( "__add__result", self.value + other.value, self.sigDigits, self.notQualified )
        else:
            return Real( "__add__result", self.qualifiedVal + other.qualifiedValue(), self.sigDigits, self.notQualified )

    def __sub__(self, other):
        "result is returned as a new Real instance with name __sub__result"
        other = self._import(other)
        if self.notQualified:
            return Real( "__sub__result", self.value - other.value, self.sigDigits, self.notQualified )
        else:
            return Real( "__sub__result", self.qualifiedVal - other.qualifiedValue(), self.sigDigits, self.notQualified )

    def __mul__(self, other):
        "result is returned as a new Real instance with name __mul__result"
        other = self._import(other)
        if self.notQualified:
            return Real( "__mul__result", self.value * other.value, self.sigDigits, self.notQualified )
        else:
            return Real( "__mul__result", self.qualifiedVal * other.qualifiedValue(), self.sigDigits, self.notQualified )

    def __truediv__(self, other):
        "result is returned as a new Real instance with name __div__result"
        #   check for divide by zero
        other = self._import(other)
        if self.notQualified:
            return Real( "__truediv__result", self.value / other.value, self.sigDigits, self.notQualified )
        else:
            return Real( "__truediv__result", self.qualifiedVal / other.qualifiedValue(), self.sigDigits, self.notQualified )

    def __abs__(self):
        "result is returned as a new Real instance with name __abs__result"
        if self.notQualified:
            if self.value < 0:
                absValue = -self.value
            else:
                absValue = self.value
            return Real( "__abs__result", absValue, self.sigDigits, self.notQualified )
        else:
            if self.qualifiedVal < 0:
                absValue = -self.qualifiedVal
            else:
                absValue = self.qualifiedVal
            return Real( "__abs__result", absValue, self.sigDigits, self.notQualified )

    def __str__(self):
        if self.notQualified:
            return str(self.value)
        else:
            return str(self.qualifiedVal)


class Imaginary:

    registry    = {}

    def __init__(self, name, coefficient):
        self.name           = None
        self.coefficient    = None
        if name != None and isinstance(name, str) and coefficient != None and \
                (isinstance(coefficient,int) or isinstance(coefficient, float) or isinstance(coefficient,Real)):
            self.name           = name
            self.coefficient    = coefficient
            Imaginary.registry[name] = self

    def set(self, coefficient):
        if coefficient != None and (isinstance(coefficient,int) or isinstance(coefficient, float) or isinstance(coefficient,Real)):
            self.coefficient    = coefficient
        return self

    def __str__(self):
        return str(self.coefficient) + "i"

class Complex:

    registry    = {}

    def __init__(self, name, x, y):
        "z = x + iy"
        self.x          = None
        self.y          = None
        self.name       = None

        if name != None and isinstance(name, str) and x != None and (isinstance(x, float) or isinstance(x,Real)) and \
                        y != None and (isinstance(y,float) or isinstance(y,Real)):
            self.name       = name
            self.x          = x
            self.y          = y
            Complex.registry[name]   = self

    def set(self, x=None,y=None):
        if x != None and (isinstance(x, float) or isinstance(x,Real)):
            self.x  = x
        if y != None and (isinstance(y, float) or isinstance(y,Real)):
            self.y  = y
        return self

    def __str__(self):
        return str(self.x) + " + " + str(self.y) + "i"
#5
(Feb-03-2018, 11:11 PM)kmcollins Wrote: Warning: this code has not been tested.
"If it's not tested, it's broken." (Bruce Eckel)
#6
How is your code compare to NumPy?
Some examples would be nice.

Rational vs python Fraction ?
99 percent of computer problems exists between chair and keyboard.
#7
The code is not tested, and it is not licensed to anyone. If you wish to contribute, I can negotiate a co-development agreement with you. I am not handing out free code which is tested and ready to be stolen.

Python's fraction.Fraction class makes no attempt to qualify the precision of the number using significant digits or to determine a limit to the decimal places considered accurate based on the experimental or measurement method. Also, Python's floating point math implementation introduces small errors in highly precise numbers and there needs to be a way of detecting this and properly qualifying results. This package is intended for laboratory science. In my documentation in the header I state that this is the purpose of writing this package. I will also be adding error estimation. The algebra package using this API should be able to provide information on the precision of results of algebraic operations on the measurement data as well as error estimates.
#8
Numpy is a python wrapper around a C implementation of matrix math. That means it uses pointer math for speed. This is very easy for a virus to exploit. Your C professor didn't warn you about this? I would rather have a pure Python implementation of the same features and be able to tell my client it's secure. Also, if you thought that programming language implementations of floating point arithmetic weren't error prone then your Professors omitted essential information.
With numpy you have no control over how mathematical operations are done and no control over the type translation from C types to Python native types. Both can introduce errors that you have no information regarding. A pure Python implementation can monitor and control both the particular mathematics done and the particular types and type translations used.
You gain security, precision, and better estimate of error by implementing in pure Python. Dance
#9
You can't control anything. You have to write your own operating system with your own CPython to avoid what you are talking about. You can't rewrite any piece of software or library because you can't trust someone. And even that won't work. Perhaps you are familiar with Intel ME? If not read this article. It's one of the many.
"As they say in Mexico 'dosvidaniya'. That makes two vidaniyas."
https://freedns.afraid.org
#10
I wrote a virtual CPU in Java a few years ago. You can do exactly correct math with a couple of registers for the number and one for the exponent. With 64 bits you can get 20 significant digits and the exponent scales it. Using addition and subtraction for multiplication and division provides exactly correct results all of the time. A 32-bit float, which is what Python uses, can have at most 10 significant digits, and multiplication and division, as implemented in the usual programming language or FPU, always produces error in the lowest order digit(s). Approximation of roots only requires an iterative refinement algorithm, like Newton's square root algorithm, to get to the level of accuracy desired.
You can and should rewrite it if you don't trust the author.


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020