From 57b59035bcc0b4c5299c48cdd339d308d5b68927 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Fri, 6 May 2022 16:51:20 -0700 Subject: [PATCH 1/4] initial work with a (semi) functional phasor class --- electricpy/_phasor.py | 213 ++++++++++++++++++++++++++++++++++++++++++ electricpy/phasors.py | 3 +- setup.py | 2 +- 3 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 electricpy/_phasor.py diff --git a/electricpy/_phasor.py b/electricpy/_phasor.py new file mode 100644 index 00000000..0196cb25 --- /dev/null +++ b/electricpy/_phasor.py @@ -0,0 +1,213 @@ +################################################################################ +"""Test Phasor Class.""" +################################################################################ + +# Import Required Packages +import numpy as _np +import cmath as _c + +class Phasor(complex): + """ + Phasor Class - An extension of the Python complex type for scientific work. + + .. warn:: + This module is still under development and should be used with caution. + + Its interfaces may change at any time without notice, its primary goal is + exploritory work, and may be experimented with, used (carefully), and may + be used to solicit feedback by way of project issues in Github: + https://github.com/engineerjoe440/ElectricPy/issues + + This class can be used in place of, or alongside the standard `complex` type + in Python to represent complex numbers as phasors (magnitude and angle) for + scientific computation and electrical engineering analysis and work. + + Examples + -------- + >>> from electricpy._phasor import Phasor + >>> Phasor(67, 120) # 67 volts at angle 120 degrees + Phasor(magnitude=67, angle=120) + >>> volt = Phasor(67, 120) + >>> print(volt) + 67 ∠ 120° + + Properties + ---------- + mag: float + The phasor magnitude, also commonly known as its absolute value. + ang: float + The phasor angle, expressed in degrees. + real: float + The real component of the complex value. + imag: float + The imaginary component of the complex value. + complex: complex + The complex type-cast of the phasor. + """ + + __magnitude: float + __angle: float + + def __init__(self, magnitude, angle=None): + """ + Phasor Constructor. + + Parameters + ---------- + magnitude: float + The phasor magnitude to express. + angle: float + The phasor angle to express, in degrees. + """ + # Handle Passing a Complex Type Directly to Phasor + if isinstance(magnitude, complex): + magnitude, ang_r = _c.polar(magnitude) + angle = _np.degrees(ang_r) + # Load the Internal Values + self.__magnitude = magnitude + self.__angle = angle + + @property + def real(self): + """Real ( RE{} ) evaluation.""" + return self.__magnitude * _np.cos(_np.radians(self.__angle)) + + @property + def imag(self): + """Imaginary ( IM{} ) evaluation.""" + return self.__magnitude * _np.sin(_np.radians(self.__angle)) + + @property + def mag(self): + """Phasor magnitude evaluation.""" + return self.__magnitude + + @property + def ang(self): + """Phasor angle evaluation.""" + return self.__angle + + @property + def complex(self): + """Phasor representation as complex.""" + return complex(self.real, self.imag) + + def __repr__(self): + """Represent the Phasor.""" + return "Phasor(magnitude={mag}, angle={ang})".format( + mag=self.__magnitude, + ang=self.__angle + ) + + def __gt__(self, __x): + """Evaluate whether __x is greater than this.""" + # Compare Magnitudes for Phasor Types + if isinstance(__x, Phasor): + return self.__magnitude.__gt__(__x.__magnitude) + # Compare Magnitudes after first Casting `complex` to Phasor + elif isinstance(__x, complex): + return self.__gt__(Phasor(__x)) + else: + return self.__magnitude.__gt__(__x) + + def __lt__(self, __x): + """Evaluate whether __x is less than this.""" + # Compare Magnitudes for Phasor Types + if isinstance(__x, Phasor): + return self.__magnitude.__lt__(__x.__magnitude) + # Compare Magnitudes after first Casting `complex` to Phasor + elif isinstance(__x, complex): + return self.__lt__(Phasor(__x)) + else: + return self.__magnitude.__lt__(__x) + + def __ge__(self, __x): + """Evaluate whether __x is greater than or equal to this.""" + # Compare Magnitudes for Phasor Types + if isinstance(__x, Phasor): + return self.__magnitude.__ge__(__x.__magnitude) + # Compare Magnitudes after first Casting `complex` to Phasor + elif isinstance(__x, complex): + return self.__ge__(Phasor(__x)) + else: + return self.__magnitude.__ge__(__x) + + def __le__(self, __x): + """Evaluate whether __x is less than or equal to this.""" + # Compare Magnitudes for Phasor Types + if isinstance(__x, Phasor): + return self.__magnitude.__le__(__x.__magnitude) + # Compare Magnitudes after first Casting `complex` to Phasor + elif isinstance(__x, complex): + return self.__le__(Phasor(__x)) + else: + return self.__magnitude.__le__(__x) + + def __str__(self): + """Stringlify the Phasor.""" + return "{} ∠ {}°".format(self.__magnitude, self.__angle) + + def __round__(self, ndigits=0): + """Round the Phasor.""" + mag = self.__magnitude + ang = self.__angle + mag = round(mag, ndigits=ndigits) + ang = round(ang, ndigits=ndigits) + return Phasor(mag, ang) + + def __mul__(self, __x): + """Return self*__x.""" + if isinstance(__x, Phasor): + return self.complex.__mul__(__x.complex) + else: + return self.complex.__mul__(__x) + + def __truediv__(self, __x): + """Return self/__x.""" + if isinstance(__x, Phasor): + return self.complex.__mul__(__x.complex) + else: + return self.complex.__mul__(__x) + + def __abs__(self): + """Return the absolute magnitude.""" + return self.__magnitude + + def from_arraylike(arraylike): + """ + Phasor Constructor for Casting from Array Like Object. + + Use this method to create a new Phasor object from a two-item (2) long + arraylike object, (e.g., a `tuple`, `list`, or NumPy array). + """ + return Phasor(*arraylike) + + +if __name__ == "__main__": + a = Phasor(67, 120) + print(a) + print(a.real) + print(a.imag) + print(a.__repr__()) + x = Phasor.from_arraylike((67, 120)) + print(x) + print(x.real) + print(x.__repr__()) + + y = complex(1+1j) + print(y) + z = Phasor(y) + print(z) + + print("mult", Phasor(y*z)) + print("mult", Phasor(a.complex * z.complex)) + print("mult", Phasor(a * z)) + + print(round(Phasor(y+z))) + + print(x > z) + print(z > x) + print(a <= z) + print(abs(a)) + + print(complex(y)) diff --git a/electricpy/phasors.py b/electricpy/phasors.py index c856ccc9..829fee1a 100644 --- a/electricpy/phasors.py +++ b/electricpy/phasors.py @@ -22,8 +22,7 @@ def phs(ang): """ Complex Phase Angle Generator. - Generate a complex value given the phase angle - for the complex value. + Generate a complex value given the phase angle for the complex value. Same as `phase`. diff --git a/setup.py b/setup.py index fc1f76c8..60684932 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,3 @@ from setuptools import setup -setup() \ No newline at end of file +setup() From 11c36a564e86c21e5b0641ed7e7d5a5aecabfe16 Mon Sep 17 00:00:00 2001 From: Joe Stanley <33275230+engineerjoe440@users.noreply.github.com> Date: Wed, 2 Aug 2023 19:59:59 -0700 Subject: [PATCH 2/4] updated to make support better OOP --- electricpy/_phasor.py | 167 ++++++------------------------------------ 1 file changed, 23 insertions(+), 144 deletions(-) diff --git a/electricpy/_phasor.py b/electricpy/_phasor.py index 0196cb25..0b303ccc 100644 --- a/electricpy/_phasor.py +++ b/electricpy/_phasor.py @@ -10,14 +10,6 @@ class Phasor(complex): """ Phasor Class - An extension of the Python complex type for scientific work. - .. warn:: - This module is still under development and should be used with caution. - - Its interfaces may change at any time without notice, its primary goal is - exploritory work, and may be experimented with, used (carefully), and may - be used to solicit feedback by way of project issues in Github: - https://github.com/engineerjoe440/ElectricPy/issues - This class can be used in place of, or alongside the standard `complex` type in Python to represent complex numbers as phasors (magnitude and angle) for scientific computation and electrical engineering analysis and work. @@ -30,6 +22,13 @@ class Phasor(complex): >>> volt = Phasor(67, 120) >>> print(volt) 67 ∠ 120° + + Parameters + ---------- + magnitude: float + The phasor magnitude to express. + angle: float + The phasor angle to express, in degrees. Properties ---------- @@ -41,137 +40,47 @@ class Phasor(complex): The real component of the complex value. imag: float The imaginary component of the complex value. - complex: complex - The complex type-cast of the phasor. """ - __magnitude: float - __angle: float - - def __init__(self, magnitude, angle=None): + def __new__(self, magnitude, angle=None): """ Phasor Constructor. - - Parameters - ---------- - magnitude: float - The phasor magnitude to express. - angle: float - The phasor angle to express, in degrees. """ # Handle Passing a Complex Type Directly to Phasor if isinstance(magnitude, complex): magnitude, ang_r = _c.polar(magnitude) angle = _np.degrees(ang_r) - # Load the Internal Values - self.__magnitude = magnitude - self.__angle = angle - - @property - def real(self): - """Real ( RE{} ) evaluation.""" - return self.__magnitude * _np.cos(_np.radians(self.__angle)) - - @property - def imag(self): - """Imaginary ( IM{} ) evaluation.""" - return self.__magnitude * _np.sin(_np.radians(self.__angle)) + return complex.__new__( + self, + real=(magnitude * _np.cos(_np.radians(angle))), + imag=(magnitude * _np.sin(_np.radians(angle))) + ) @property def mag(self): """Phasor magnitude evaluation.""" - return self.__magnitude + return _np.absolute(self) @property def ang(self): - """Phasor angle evaluation.""" - return self.__angle - - @property - def complex(self): - """Phasor representation as complex.""" - return complex(self.real, self.imag) + """Phasor angle evaluation in degrees.""" + return _np.degrees(_np.angle(self)) def __repr__(self): """Represent the Phasor.""" - return "Phasor(magnitude={mag}, angle={ang})".format( - mag=self.__magnitude, - ang=self.__angle - ) - - def __gt__(self, __x): - """Evaluate whether __x is greater than this.""" - # Compare Magnitudes for Phasor Types - if isinstance(__x, Phasor): - return self.__magnitude.__gt__(__x.__magnitude) - # Compare Magnitudes after first Casting `complex` to Phasor - elif isinstance(__x, complex): - return self.__gt__(Phasor(__x)) - else: - return self.__magnitude.__gt__(__x) - - def __lt__(self, __x): - """Evaluate whether __x is less than this.""" - # Compare Magnitudes for Phasor Types - if isinstance(__x, Phasor): - return self.__magnitude.__lt__(__x.__magnitude) - # Compare Magnitudes after first Casting `complex` to Phasor - elif isinstance(__x, complex): - return self.__lt__(Phasor(__x)) - else: - return self.__magnitude.__lt__(__x) - - def __ge__(self, __x): - """Evaluate whether __x is greater than or equal to this.""" - # Compare Magnitudes for Phasor Types - if isinstance(__x, Phasor): - return self.__magnitude.__ge__(__x.__magnitude) - # Compare Magnitudes after first Casting `complex` to Phasor - elif isinstance(__x, complex): - return self.__ge__(Phasor(__x)) - else: - return self.__magnitude.__ge__(__x) - - def __le__(self, __x): - """Evaluate whether __x is less than or equal to this.""" - # Compare Magnitudes for Phasor Types - if isinstance(__x, Phasor): - return self.__magnitude.__le__(__x.__magnitude) - # Compare Magnitudes after first Casting `complex` to Phasor - elif isinstance(__x, complex): - return self.__le__(Phasor(__x)) - else: - return self.__magnitude.__le__(__x) + return f"Phasor(magnitude={self.mag}, angle={self.ang})" def __str__(self): """Stringlify the Phasor.""" - return "{} ∠ {}°".format(self.__magnitude, self.__angle) + angle_denotion = "∠" + return f"{self.mag} {angle_denotion} {self.ang}°" def __round__(self, ndigits=0): """Round the Phasor.""" - mag = self.__magnitude - ang = self.__angle - mag = round(mag, ndigits=ndigits) - ang = round(ang, ndigits=ndigits) - return Phasor(mag, ang) - - def __mul__(self, __x): - """Return self*__x.""" - if isinstance(__x, Phasor): - return self.complex.__mul__(__x.complex) - else: - return self.complex.__mul__(__x) - - def __truediv__(self, __x): - """Return self/__x.""" - if isinstance(__x, Phasor): - return self.complex.__mul__(__x.complex) - else: - return self.complex.__mul__(__x) - - def __abs__(self): - """Return the absolute magnitude.""" - return self.__magnitude + return Phasor( + round(self.mag, ndigits=ndigits), + round(self.ang, ndigits=ndigits) + ) def from_arraylike(arraylike): """ @@ -181,33 +90,3 @@ def from_arraylike(arraylike): arraylike object, (e.g., a `tuple`, `list`, or NumPy array). """ return Phasor(*arraylike) - - -if __name__ == "__main__": - a = Phasor(67, 120) - print(a) - print(a.real) - print(a.imag) - print(a.__repr__()) - x = Phasor.from_arraylike((67, 120)) - print(x) - print(x.real) - print(x.__repr__()) - - y = complex(1+1j) - print(y) - z = Phasor(y) - print(z) - - print("mult", Phasor(y*z)) - print("mult", Phasor(a.complex * z.complex)) - print("mult", Phasor(a * z)) - - print(round(Phasor(y+z))) - - print(x > z) - print(z > x) - print(a <= z) - print(abs(a)) - - print(complex(y)) From f7028adb3e322c51fa01db90f972b87882dc00ff Mon Sep 17 00:00:00 2001 From: Joe Stanley <33275230+engineerjoe440@users.noreply.github.com> Date: Thu, 3 Aug 2023 10:10:04 -0700 Subject: [PATCH 3/4] update to reflect angle marker where appropriate --- electricpy/_phasor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/electricpy/_phasor.py b/electricpy/_phasor.py index 0b303ccc..18c74999 100644 --- a/electricpy/_phasor.py +++ b/electricpy/_phasor.py @@ -3,6 +3,7 @@ ################################################################################ # Import Required Packages +import sys as _sys import numpy as _np import cmath as _c @@ -73,6 +74,8 @@ def __repr__(self): def __str__(self): """Stringlify the Phasor.""" angle_denotion = "∠" + if _sys.stdout.encoding != "utf-8": + angle_denotion = "/_" return f"{self.mag} {angle_denotion} {self.ang}°" def __round__(self, ndigits=0): From 5e9fbea06d81bf15a5b2d967430a28c5e229f37b Mon Sep 17 00:00:00 2001 From: Joe Stanley <33275230+engineerjoe440@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:05:25 -0700 Subject: [PATCH 4/4] update documentation and make static method --- electricpy/_phasor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electricpy/_phasor.py b/electricpy/_phasor.py index 18c74999..7e26d2fb 100644 --- a/electricpy/_phasor.py +++ b/electricpy/_phasor.py @@ -9,7 +9,7 @@ class Phasor(complex): """ - Phasor Class - An extension of the Python complex type for scientific work. + An extension of the Python `complex` type for work in polar coordinates. This class can be used in place of, or alongside the standard `complex` type in Python to represent complex numbers as phasors (magnitude and angle) for @@ -85,6 +85,7 @@ def __round__(self, ndigits=0): round(self.ang, ndigits=ndigits) ) + @staticmethod def from_arraylike(arraylike): """ Phasor Constructor for Casting from Array Like Object.