[Python-Dev] Complex numbers

TBER Abdelmalek a.tber10 at gmail.com
Mon Sep 4 17:05:16 EDT 2017


This module implements cplx class (complex numbers) regardless to the
built-in class.
The main goal of this module is to propose some improvement to complex
numbers in python and deal with them from a mathematical approach.
Also cplx class doesn't support the built-in class intentionally, as the
idea was to give an alternative to it.
With the hope I managed to succeed, here is the module :
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20170904/f47fafbc/attachment.html>
-------------- next part --------------
# Module for complex numbers (doesn't support the built-in complex class)
# Originally contributed by TBER Abdelmalek

import re, math
from fractions import Fraction as Q

if __name__ == '__main__' :
	import os, Complex
	help(Complex)
	os.system("pause")

_supported = (int, float, Q, str, tuple, list) # Supported types for instanciation and operations for cplx class
	
class cplx :

	"""This class implements complex numbers at the form of 'a+bi'
	   with a : the real part, and b : the imaginary part.
	   a and b are real numbers : [integers, floating numbers, or 
	   fractions (from Fraction class of fractions module), refered here by Q].
	   
	   Construction of complex numbers can be from two major ways :
		- cplx([real part[, imaginary part]]) : 
			Two real numbers arguments given and both optionals, so that cplx() = 0
			For fractions use Fraction class : cplx(Q(n,d),Q(n',d'))
			for details about Fraction, see help(Fraction).
			
		-cplx('valid form of a complex number in a STRING'):
			The advantage of this way is the ease of fractions use with '/'.
			Examples : cplx('2i-1/4')
			           cplx('6 - 1/5 * i') # spaces don't bother at all
			           cplx('7i')
	"""	
	global _supported
	
	def __init__(self, a=0, b=None):
		if isinstance(a, str) and b is None:  # construction from string
			if a == '' :
				self.re = 0
				self.im = 0
			elif cplx.verify(a):
				part_one = ''
				part_two = ''
				switch = False
				first = True
				for c in a :
					if c in ('+', '-') and not first : 
						switch = True
						part_two += c
					elif not switch :
						if c.isalnum() or c in ('+', '-', '/') : part_one += c
						elif c in (',', '.') : part_one += '.'
					else :
						if c.isalnum() or c == '/' : part_two += c
						elif c in (',', '.') : part_two += '.'
					first = False
				if 'i' in part_two :
					part_two = part_two[:len(part_two)-1]
					if '.' in part_one : self.re = float(part_one)
					elif '/' in part_one : self.re = Q(part_one)
					else : self.re = int(part_one)
					if '.' in part_two : self.im = float(part_two)
					elif '/' in part_two : self.im = Q(part_two)
					elif part_two == '+' : self.im = 1
					elif part_two == '-' : self.im = -1
					else : self.im = int(part_two)
				elif 'i' in part_one :
					part_one = part_one[:len(part_one)-1]
					if part_two == '' : self.re = 0
					elif '.' in  part_two : self.re = float(part_two)
					elif '/' in part_two : self.re = Q(part_two)
					else : self.re = int(part_two)
					if '.' in part_one : self.im = float(part_one)
					elif '/' in part_one : self.im = Q(part_one)
					elif part_one == '' or part_one == '+' : self.im = 1
					elif part_one == '-' : self.im = -1
					else : self.im = int(part_one)
				else :
					if '.' in part_one : self.re = float(part_one)
					elif '/' in part_one : self.re = Q(part_one)
					else : self.re = int(part_one)
					self.im = 0 
			else :
				raise ValueError("The form of complex numbers should be such as : 'a+bi' with a, b integers, floating numbers or fractions.")
		elif isinstance(a, (tuple,list)) and len(a) == 2 and b is None:   # construction from 2-tuples or list
			self.re = a[0]
			self.im = a[1]
		elif isinstance(a, cplx) and b is None :   # construction from cplx(complex)
			self.re = a.re
			self.im = a.im
		elif isinstance(a, (int, float, Q)) :      # construction from integers, fractions and floating numbers
			self.re = a
			if b is None : self.im = 0
			elif isinstance(b, (int, float, Q)) : self.im = b
			else : raise TypeError("Imaginary part sould be an integer, floating number or fraction .")
		else :
			raise TypeError("Invalid arguments! For details see help(cplx).")
			
	def __setattr__(self, n, v):
		if n not in ('re', 'im') : raise AttributeError("Invalid attribute.")
		if isinstance(v, (int, float, Q)) : object.__setattr__(self, n, v)
		else : raise TypeError("Illegal assignement.")
		
	def __delattr__(self, n):
		raise AttributeError("cplx instances are characterized by 're' and 'im' attributes, deleting them is impossible.")
			
	def __repr__(self):
		"""Returns repr(self) in an elegant way."""
		chain = ''
		if isinstance(self.re, Q) and self.re._numerator != 0 :
			if self.re._denominator != 1 :
				if self.re._numerator < 0 : chain += '-'
				chain += '({}/{})'.format(abs(self.re._numerator), self.re._denominator)
			else : chain += '{}'.format(int(self.re))
		elif self.re != 0 : chain += '{}'.format(self.re)
		if self.re != 0 and self.im > 0 : chain += '+'
		elif self.im < 0 : chain += '-'
		if isinstance(self.im, Q) and self.im._numerator != 0 :
			if self.im._denominator != 1 :
				chain += '({}/{})'.format(abs(self.im._numerator), self.im._denominator)
			elif abs(self.im) != 1 : chain += '{}'.format(abs(int(self.im)))
		elif self.im != 0 and abs(self.im) != 1 : chain += '{}'.format(abs(self.im))
		if self.im != 0 : chain += 'i'
		if chain == '' : chain = '0'
		return chain
	
	def __str__(self):
		"""Returns str(self)"""
		return repr(self)
		
	def __int__(self):
		"""Returns int(real part)"""
		return int(self.re)
		
	def __float__(self):
		"""Returns float(real part)"""
		return float(self.re)
	
	def __bool__(self):
		"""Returns self != 0"""
		return self != 0
		
	def __pos__(self):
		"""+self"""
		return self
	
	def __neg__(self):
		"""-self"""
		return cplx(-self.re, -self.im)
		
	def __abs__(self):
		"""Returns the absolute value if self is a real number.
		   Returns its modulus if it is complex."""
		if self.im == 0 : return cplx(abs(self.re))
		else : return self.modul()
		
# Comparaison block
	def __eq__(self, value):
		"""self == value"""
		if isinstance(value, (_supported, cplx)) :
			value = cplx(value)
			return self.re == value.re and self.im == value.im
		else : raise TypeError("This type : {} is not supported for this operation.".format(type(value)))
		
	def __ne__(self, value):
		"""self != 0"""
		return not self == value
		
	def __gt__(self, value):
		"""self > value"""
		if isinstance(value, (_supported, cplx)) :
			value = cplx(value)
			if self.im == 0 and value.im == 0 : return self.re > value.re	
			else : raise ValueError("Only real numbers are to be compared.")
		else : raise TypeError("This type : {} is not supported for this operation.".format(type(value)))
	
	def __ge__(self, value):
		"""self >= value"""
		return self > value or self == value
		
	def __lt__(self, value):
		"""self < value"""
		return not self >= value
		
	def __le__(self, value):
		"""self <= value"""
		return not self > value
	
# Operation block
	def __add__(self, value):
		"""self + value"""
		if isinstance(value, (_supported, cplx)) :
			value = cplx(value)
			return cplx(self.re + value.re, self.im + value.im)
		else : raise TypeError("This type : {} is not supported for this operation.".format(type(value)))
		
	def __radd__(self, value):
		"""value + self"""
		return self + value
		
	def __sub__(self, value):
		"""self - value"""
		return self + (-value)
		
	def __rsub__(self, value):
		"""value - self"""
		return -self + value
		
	def __mul__(self, value):
		"""self * value"""
		if isinstance(value, (_supported, cplx)) :
			value = cplx(value)
			return cplx(self.re*value.re - self.im*value.im, self.re*value.im + self.im*value.re)
		else : raise TypeError("This type : {} is not supported for this operation.".format(type(value)))
		
	def __rmul__(self, value):
		"""value * self"""
		return self * value
	
	def __pow__(self, value):
		"""self**value"""
		if self.im == 0 : return cplx(self.re**value)
		elif isinstance(value, (_supported, cplx)) :
			value = cplx(value)
			if value == int(value):
				if value == 0 : return 1
				z = self
				x = 1
				while x < abs(value) : 
					z *= self
					x += 1
				if value > 0 : return z
				else : return 1/z
			else :
				return math.e**(value * (math.log(self.modul()) + self.arg()*cplx('i')))		
		else : raise TypeError("This type : {} is not supported for this operation.".format(type(value)))
		
	def __rpow__(self, value):
		"""value**self"""
		if self.im == 0 : return value**self.re
		elif value == 0 : return 1 if self == 0 else 0
		elif isinstance(value, (int, float, Q)) :
			return (value**self.re)*(math.cos(self.im*math.log(value)) + math.sin(self.im*math.log(value))*cplx('i'))
		else : raise TypeError("This type : {} is not supported for this operation.".format(type(value)))
		
	def __truediv__(self, value):
		"""self/value : real and imaginary parts are left as fractions.
		   Use c_float converter method to have floating numbers instead of fractions."""
		if value == 0 : raise ZeroDivisionError
		elif isinstance(value, (_supported, cplx)) :
			value = cplx(value)
			return cplx(Q(self.re*value.re + self.im*value.im, value.re**2 + value.im**2), Q(self.im*value.re - self.re*value.im, value.re**2 + value.im**2))
		else : raise TypeError("This type : {} is not supported for this operation.".format(type(value)))
		
	def __rtruediv__(self,value):
		"""value/self"""
		if isinstance(value, (_supported, cplx)) : return cplx(value)/self
		else : raise TypeError("This type : {} is not supported for this operation.".format(type(value)))
	
	def __floordiv__(self, value):
		"""self//value"""
		return (self/value).c_int()
		
	def __rfloordiv__(self, value):
		"""value//self"""
		return (value/self).c_int()
		
	def __mod__(self, value):
		"""self % value"""
		return self - (self//value)*value
		
	def __rmod__(self, value):
		"""value % self"""
		return value - (value//self)*self
		
	def __divmod__(self, value):
		"""divmod(self, value)"""
		return (self//value, self % value)
		
	def __rdivmod__(self, value):
		"""divmod(value, self)"""
		return (value//self, value % self)
		
# Converting methods for complex
	def c_int(self):
		"""Converts real and imaginary parts into integers (returns a new object)"""
		return cplx(int(self.re), int(self.im))
	
	def c_float(self):
		"""Converts real and imaginary parts into floating numbers (returns a new object)"""
		return cplx(float(self.re), float(self.im))
	 
	def c_fract(self):
		"""Converts real and imaginary parts into fractions (returns a new object)"""
		return cplx(Q.from_float(float(self.re)), Q.from_float(float(self.im)))
		
# Useful methods
	def coord(self):
		"""Returns the coordinates of a complex number in a tuple.
		   a+bi -> (a,b)"""
		return (self.re, self.im)
	
	def conjugate(self):
		"""Returns the conjugate of a complex number.
		   a+bi -> a-bi"""
		return cplx(self.re, -self.im)
	
	def switch(self):
		"""Returns a complex number with real part and imaginary part switched.
		   a+bi -> b+ai"""
		return cplx(self.im, self.re)
	
	def modul(self):
		"""Returns the modulus of the complex number"""
		return math.sqrt(self.re**2 + self.im**2)
		
	def arg(self):
		"""Returns the argument of the complex number in radian"""
		if self != 0 : teta = math.acos(abs(self.re)/abs(self))
		if self.re >= 0 and self.im > 0 : angle = teta
		elif self.re > 0 and self.im <= 0 : angle = -teta
		elif self.re < 0 and self.im >= 0 : angle = math.pi - teta
		elif self.re <= 0 and self.im < 0 : angle = -math.pi + teta
		else : raise ValueError("0 doesn't have an argument.")
		return angle
		
	def deg_arg(self):
		"""Returns the argument of the complex number in degrees"""
		return math.degrees(self.arg())
		
	def polar(self):
		"""Returns the trigonometric form of a complex number -> str"""
		return "{}(cos({}) + sin({})i)".format(self.modul(), self.arg(), self.arg())
		
	def factorise(self, value=1):
		"""Returns a factorisation of a complex number by a value given (similar to divmod) -> str
		   Example : (2i).factorise(i+1) >>> 2i = (i+1)(i+1)+0"""
		chain = "{} = ({})({})".format(self, value, self//value)
		if str(self%value)[0] == '-' : chain += "{}".format(self%value)
		else : chain += "+{}".format(self%value)
		return chain
		
	@classmethod
	def verify(cls, phrase) :
		"""verify('a+bi') -> bool
		   The argument must be provided in a string to verify whether it's a valid complex number or not.
		   a and b can be integers, fractions(n/d), or floating numbers(with . or ,)"""
		if isinstance(phrase, str) :
			phrase = phrase.replace(" ", "")
			model_re = r"[+-]?[0-9]+([,./][0-9]+)?([+-]([0-9]+([,./][0-9]+)?[*]?)?i)?" # a real part and an optional imaginary part
			model_im = r"[+-]?([0-9]+([,./][0-9]+)?[*]?)?i([+-][0-9]+([,./][0-9]+)?)?" # an imaginary part and an optional real part
			return re.fullmatch(model_re, phrase) is not None or re.fullmatch(model_im, phrase) is not None
		else : raise TypeError("The complex number must be given in a string.")

#---------------------------------------#
i = cplx(0,1) # Most basic complex number
#---------------------------------------#


More information about the Python-Dev mailing list