Clases
Contents
9. Clases#
En Python, todo es un objeto, es decir, una instancia o realización de un clase. Cada objeto tiene unos métodos:
objeto.metodo(...)
que corresponde a funciónes internas del objeto, y también puede tener _atributos_:
objeto.atributo
que son variables internas dentro del objeto: self.atributo=....
.
Algunos objetos en Python también pueden recibir parámetros de entrada a modo de claves de un diccionario interno del objeto
objeto['key']='value'
entro otras muchas propiedades
Ejemplos de objetos son:
int
: Enterosfloat
: Números punto flotante (floating point numbers)str
: Cadenas de caractereslist
: Listasdict
: Diccionarios.
Si el tipo de objeto es conocido, uno pude comprobar si un objeto determinado corresponde a ese tipo con isinstance
:
s='hola mundo'
print('Is `s` an string?: {}'.format( isinstance(s,str)) )
print('Is `s` a float?: {}'.format( isinstance(s,float)) )
Is `s` an string?: True
Is `s` a float?: False
Dentro del paradigma de objetos es posible agrupar diferentes conjuntos de variables de modo que el nombre de un atributo a método adquiere dos partes, una parte principal, que en el análogo con el nombre completo de una persona podríamos asimilar a su apellido, y el método o atributo, que sería como el primer nombre, separados por un punto:
Método:
last_name.first_name()
Atributo:
last_name.first_name
.
Esto nos permite tener objetos dentro de un programa con igual nombre, pero diferente appellido. Como por ejemplo, los diferentes \(\cos(x)\) que vienen en los diferentes módulos matématicos implementados en Python. Por eso es recomendado cargar los módulos manteniendo el espacio de nombres (que en nuestra analogía sería el espacio de apellidos). Cuando el modulo tenga un nombre muy largo (más de cuatro caracteres), se puede usar una abreviatura lo suficientemente original para evitar que pueda ser sobreescrita por un nuevo objeto:
import math as math
import numpy as np
k=3 #N/m
m=2 #Kg
A=3 #m
t=2 #s
ω=np.sqrt(k/m) #rad/s
#ver https://pyformat.info/
print('x(t)={:.2f} m'.format(
A*np.cos( ω*t )
))
print('x(t)={:.2f} m'.format(
A*math.cos( ω*t )
))
x(t)=-2.31 m
x(t)=-2.31 m
Note que import math as m
entraría en conflicto con la definición de m
en m=2
La forma recomendada de importar los diferentes módulos y el uso de sus métodos y atributos suele resumirse en Cheat Sheets. Para Python científico recomendamos las elaboradas por Data Camp, que pueden consultarse aquí
Para programación de Python en general se recomienda el estándar PEP 8 de Python
Antes de comenzar con las clases, es conveniente resumir el paradigma de programación funcional:
9.1. Programación funcional#
En cálculo científico el paradigma funcional, en el cual el programa se escribe en términos de funciones, suele ser suficiente.
El esqueleto de un programa funcional es típicamente del siguiente tipo
#!/usr/bin/env python3
import somemodule as sm
def func1(...):
'''
Ayuda func1
'''
.....
def func2(...):
'''
Ayuda func2
'''
.....
def main(...):
'''
Ayuda función principal
'''
x=func1(...)
y=func2(x)
if __name__=='__main__':
z=main(...)
print('El resultado final es: {}'.format(z))
Para su diseño, el programa se debe separar sus partes independientes y para cada una de ellas se debe definir una función.
La función ideal es una que se pueda reutilizar facilamente en otro contexto.
La última función, main(...)
combina todas las anteriores para entregar el resultado final del programa.
La instrucción
if __name__=='__main__'
permite que el programa pueda ser usado también como un módulo de Python, es decir que se pueda cargar desde otro programa con el import
. En tal caso, la variable
interna de Python __name__
es diferente a la cadena de caracteres '__main__'
y esa parte del programa no se ejecuta. Dentro de Jupyter:
__name__
'__main__'
Ejemplo módulo
%%writefile example.py
#!/usr/bin/env python3
print( f'check: {__name__}')
def hola():
print( f'check {__name__} como módulo:')
print('mundo')
if __name__=='__main__':
hola()
Overwriting example.py
ls
funciona
ls -l example.py
-rwxr-xr-x 1 restrepo restrepo 171 Mar 31 14:11 example.py*
cat
funciona
cat example.py
#!/usr/bin/env python3
print( f'check: {__name__}')
def hola():
print( f'check {__name__} como módulo:')
print('mundo')
if __name__=='__main__':
hola()
Cambia los permisos a ejecución
! chmod a+x example.py
ls -l example.py
-rwxr-xr-x 1 restrepo restrepo 171 Mar 31 14:11 example.py*
Corre el programa desde la consola
!./example.py
check: __main__
check __main__ como módulo:
mundo
Uso como módulo
import example
check: example
example.hola()
check example como módulo:
mundo
Ejecutarlo desde una celda de Jupyter es equivalente a ejecutarlo desde la consola
#!/usr/bin/env python3
print( 'check __name__: {}'.format(__name__))
def hola():
print('mundo')
if __name__=='__main__':
hola()
check __name__: __main__
mundo
%%writefile example.py
#!/usr/bin/env python3
import sys
if __name__=='__main__':
print(sys.argv)
Overwriting example.py
!./example.py df ll 1 3
['./example.py', 'df', 'll', '1', '3']
9.2. Clases#
Aunque en Python se puede trabajar directamente con objetos, en general un objeto es una instancia de un clase. Es decir, debe incializarse a partir de una Clase. Esto típicamente involucra ejecutar varios métodos e inicializar varios atributos bajo el espacio de nombres del objeto incializado. Por ejemplo, para inicializar las clases ‘int’, ‘float’, ‘str’, ‘list’,’dict’
n=int() ## ⬄ to n=0
n
0
int
es la claseint()
es la instancia de la clasen=int()
es el objeto asociado a la clase:n.→
contiene todos los atributos y los métodos de la claseint
x=float(6) ## ⬄ x=3.
x
6.0
l=list() ## ⬄ l=[]
l
[]
d=dict() ## ⬄ d={}
d
{}
#tupla vacía
A=set()
La principal motivación para escribir una clase en lugar de una función, es que la clase puede ser la base para generar nuevas clases que hereden los métodos y atributos de la clase original.
Por ejemplo el DataFrame de Pandas es una clase que puede ser inicializada de múltiples formas. Además, está diseñada para que pueda ser extendida facilmente: https://pandas.pydata.org/pandas-docs/stable/development/extending.html
import pandas as pd
In[1]: pd.DataFrame??
...
class DataFrame(NDFrame):
...
## ----------------------------------------------------------------------
## Constructors
def __init__(self, data=None, index=None, columns=None, dtype=None,
copy=False):
if data is None:
...
Como puede verse, un DataFrame
es una subclase (es decir, un caso especial) de NDFrame
.
Una instancia u objeto de la clase DataFrame
, df
a continuación, se puede inicializar de diferentes maneras
df=pd.DataFrame()
df
df=pd.DataFrame([{'A':1,'B':2}])
df
A | B | |
---|---|---|
0 | 1 | 2 |
df=pd.DataFrame({'A':[1],'B':[2]})
df
A | B | |
---|---|---|
0 | 1 | 2 |
9.2.1. Programación por clases#
Una clase se puede pensar como un conjuto de funciones y atributos que comparten algo en común.
Algunas veces, cuando la complejidad del problema se puede descomponer en una estructura de capas, donde la capa interna es la mas simple y las capas más externas van aumentando la complejidad, pude ser conveniente pensar en una estructura de clases.
De hecho, la clase básica puede heredar todas sus propiedades a subclases basadas en ella.
El espacio de nombres asociado a la clase se define con el nombre génerico de self
el cual toma el nombre de las instancia (objeto) asociada a la inicialización de la clase.
Las variables globales de la clase pasán a ser automáticamente atributos del objeto, y nuevo atributos de pueden definir dentro del espacio de nombres self
dentro de cada función de la clase.
Esqueleto:
class clasenueva:
'''
Ayuda de la clase
'''
var1='valor' #variable global → atributo de la clase
def func1(self,...): #método de la clase
'''
Ayuda del método
'''
self.var2='hola mundo' #atributo de la clase
....
def func2(self,....):
'''
Ayuda del método
'''
print(self.var1)
print(self.var2)
....
def main(self,....):
....
Cada una de las funciones pasan a ser métodos de la clase, mientras que cada una de la variables globales y con espacio de nombre self
pasan a ser atributos de la clase.
Ejemplo:
class clasenueva:
var='hola'
var2=[]
def main(self):
self.var=self.var+' mundo'
self.var3=self.var2+[3]
print(self.var)
Creación del objeto c
, de manera que self
→ c
. c
es una instancia de la clase clasenueva
a continuación:
c=clasenueva()
c.var
'hola'
main
es un método de la clase
c.main()
hola mundo
var
es un atributo de la clase
c.var
'hola mundo'
c.var3
[3]
Para la creación de clases disponemos de métodos especiales, constructores, que podemos definir para adicionar “magia” a nuestras clases. Estas están simpre rodeadas de un doble guión bajo, por ejemplo: __init__
o __lt__
. Una lista completa de ellos con su explicación de uso se puede encontrar en 1.
Resaltamos a continuación algunos de ellos
__init__
: Se ejecuta automáticamente al inicializar la clase__add__
: Sobrecarga el operador suma,+
→ self + other
For example, with __init__
, the previous class is
class clasenueva:
def __init__(self):
self.var='hola'
self.var2=[]
def main(self):
self.var=self.var+' mundo'
self.var3=self.var2+[3]
print(self.var)
c=clasenueva()
c.var
'hola'
9.2.2. Herencia#
Los métodos especiales se pueden heredar automáticamente desde la clase inicial, que llamaremos superclass
. Para ello inicializamos la clase con la superclass
como argumento, es decir, en forma genérica como:
class subclass(superclass):
...
import copy
import random
class animal:
stage='baby' #or kid or adult
sex='male'
eyes=2
live=True
def __add__(self,other):
if (type(self)==type(other) and
self.stage=='adult' and
other.stage=='adult' and
self.sex!=other.sex):
baby=copy.copy(self)
baby.stage='baby'
baby.sex=random.choice(['female','male'])
if isinstance(self,insect):
baby.stage=='larvae'
return baby
else:
return None
class insect(animal):
def __init__(self,stage='larvae',sex='male',wings=0,
legs=0,anntenaes=0,eyes=0,stings=0):
self.stage=stage
self.sex=sex
self.wings=wings
self.legs=legs
self.anntenaes=anntenaes
self.eyes=eyes
self.stings=stings
if self.stage=='baby':
self.stage=='larvae'
self.bones=False
class vertebrate(animal):
bones=True
class bird(vertebrate):
def __init__(self,stage='baby',sex='male'):
self.stage=stage
self.sex=sex
self.wings=2
self.legs=2
self.eyes=2
self.feathers=True
butterfly=insect(stage='adult',wings=2,legs=6,anntenaes=2,eyes=1700)
pigeon = bird(stage='adult',sex='female')
other_pigeon=bird(stage='adult',sex='male')
butterfly.live
True
#__add__( self , other )
squab = pigeon + other_pigeon
squab.stage,squab.sex
('baby', 'male')
pigeon+pigeon
butterfly+pigeon
Cree una clase mamal
o una clase fish
9.2.3. Reescribir método de superclass
#
class animal:
def __init__(self):
self.stage='baby' #or kid or adult
self.sex='male'
self.eyes=2
self.live=True
def __add__(self,other):
if (type(self)==type(other) and
self.stage=='adult' and
other.stage=='adult' and
self.sex!=other.sex):
baby=copy.copy(self)
baby.stage='baby'
baby.sex=random.choice(['female','male'])
if isinstance(self,insect):
baby.stage=='larvae'
return baby
else:
return None
class insect(animal):
def __init__(self,stage='larvae',sex='male',wings=0,
legs=0,anntenaes=0,eyes=0,stings=0):
self.stage=stage
self.sex=sex
self.wings=wings
self.legs=legs
self.anntenaes=anntenaes
self.eyes=eyes
self.stings=stings
if self.stage=='baby':
self.stage=='larvae'
self.bones=False
butterfly=insect(stage='adult',wings=2,legs=6,anntenaes=2,eyes=1700)
butterfly.live
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In [169], line 2
1 butterfly=insect(stage='adult',wings=2,legs=6,anntenaes=2,eyes=1700)
----> 2 butterfly.live
AttributeError: 'insect' object has no attribute 'live'
9.2.4. Use un método de la superclass
con super
#
Si lo que queremos es modificar el comportomiento de un método de una superclase entonces debemos usar la función super
.
Para mantener la herencia el comportamiento de los métodos de una clase inicial, ls super clase, se usa la función super
que mantiene la herencia. Ver:
https://realpython.com/python-super/ (Backup)
https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ (Backup)
super()
gives you access to methods in a superclass from the subclass that inherits from it.
super()
is the same assuper(__class__, <first argument>)
: the first is the subclass, and the second parameter is an object that is an instance of that subclass.
La estructura general es como la siguiente, basada en Inherited class variable modification in Python:
class subclass(superclass):
def __special__(self,*args, **kwargs):
#Modifications to __especial__ here
...
super(subclass, self).__special__(*args, **kwargs)
...
Modificar la clase animal
con un __init__
y generar las subclases apropiadamente
class animal:
def __init__(self):
self.stage='baby' #or kid or adult
self.sex='male'
self.eyes=2
self.live=True
def __add__(self,other):
if (type(self)==type(other) and
self.stage=='adult' and
other.stage=='adult' and
self.sex!=other.sex):
baby=copy.copy(self)
baby.stage='baby'
baby.sex=random.choice(['female','male'])
if isinstance(self,insect):
baby.stage=='larvae'
return baby
else:
return None
class insect(animal):
'''
The pointers `*args` and `**kwargs` are not really necessary
because the `__init__` in superclass does not have any
'''
def __init__(self,*args,stage='larvae',sex='male',wings=0,
legs=0,anntenaes=0,eyes=0,stings=0,**kwargs):
'''
stage='larvae',sex='male',wings=0,
legs=0,anntenaes=0,eyes=0,stings=0
'''
super(insect, self).__init__(*args,**kwargs)
self.stage=stage
self.sex=sex
self.wings=wings
self.legs=legs
self.anntenaes=anntenaes
self.eyes=eyes
self.stings=stings
if self.stage=='baby':
self.stage=='larvae'
self.bones=False
butterfly=insect(stage='adult',wings=2,legs=6,anntenaes=2,eyes=1700)
butterfly.live
True
class animal:
def __init__(self,live=True):
self.stage='baby' #or kid or adult
self.sex='male'
self.eyes=2
self.live=live
def __add__(self,other):
if (type(self)==type(other) and
self.stage=='adult' and
other.stage=='adult' and
self.sex!=other.sex):
baby=copy.copy(self)
baby.stage='baby'
baby.sex=random.choice(['female','male'])
if isinstance(self,insect):
baby.stage=='larvae'
return baby
else:
return None
class insect(animal):
'''
The pointers `*args` and `**kwargs` are necessary
to access to the `**kwargs` of the `__init__` in superclass
'''
def __init__(self,*args,stage='larvae',sex='male',wings=0,
legs=0,anntenaes=0,eyes=0,stings=0,**kwargs):
'''
stage='larvae',sex='male',wings=0,
legs=0,anntenaes=0,eyes=0,stings=0
'''
super(insect, self).__init__(*args,**kwargs)
self.stage=stage
self.sex=sex
self.wings=wings
self.legs=legs
self.anntenaes=anntenaes
self.eyes=eyes
self.stings=stings
if self.stage=='baby':
self.stage=='larvae'
self.bones=False
butterfly=insect(stage='adult',wings=2,legs=6,anntenaes=2,eyes=1700,live=False)
butterfly.live
False
9.3. Clase vector#
Comenzaremos definiendo un alias de clase list
que llameremos vector y que funcione exactamente igual que la clase lista. Todas los métodos especiales se heredan automáticamente
class vector(list):
pass
l=list()
v=vector()
v.append(1)
v
[1]
l1=[1,2]
l2=[3,4]
l1+l2
[1, 2, 3, 4]
Vamos a inicializar dos instancias de la clase vector
y comprobar que suman como listas
v1=vector(l1)
v2=vector(l2)
v1+v2
[1, 2, 3, 4]
Ahora reemplazaremos el método mágico __add__
de la lista para que el operador +
realice la suma vectorial:
l1,l2
([1, 2], [3, 4])
map?
Init signature: map(self, /, *args, **kwargs)
Docstring:
map(func, *iterables) --> map object
Make an iterator that computes the function using arguments from
each of the iterables. Stops when the shortest iterable is exhausted.
Type: type
Subclasses:
list(map(lambda x,y:x+y,l1,l2))
[4, 6]
class vector(list):
def __add__(self, other):
'''
__add__ asocia la operación del símbolo '+'
'''
return vector(map(lambda x,y: x+y, self, other))
v1=vector(l1)
v2=vector(l2)
v1+v2
[4, 6]
v1-v2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [182], line 1
----> 1 v1-v2
TypeError: unsupported operand type(s) for -: 'vector' and 'vector'
class vector(list):
def __add__(self, other):
'''
__add__ asocia la operación del símbolo '+'
'''
return vector(map(lambda x,y: x+y, self, other))
def __sub__(self, other):
'''
__sub__ asocia la operación del símbolo '-'
'''
return vector(map(lambda x,y: x-y, self, other))
Reiniciando el kernel de jupyter
v1=vector([5,8])
v2=vector([3,6])
v1+v2
[8, 14]
v1-v2
[2, 2]
butterfly=insect(stage='adult',wings=2,legs=6,anntenaes=2,eyes=1700)
butterfly.live
True
Como ejemplo, vamos a implemetar el atributo modulus
a la clase vector
dentro del método especial __init__
:
import math
class vector(list):
def __init__(self,*args, **kwargs):
'''
Add the modulus of a vector at initialization
'''
try:
l=args[0]
self.modulus=math.sqrt( sum( map( lambda l: l*l,l )) )
except:
self.modulus=0
super(vector, self).__init__(*args, **kwargs)
def __add__(self, other):
'''
__add__ asocia la operación del símbolo "+"
'''
return vector(map(lambda x,y: x+y, self, other))
def __sub__(self, other):
'''
__sub__ asocia la operación del símbolo "-"
'''
return vector(map(lambda x,y: x-y, self, other))
v1=vector( [3,2] )
v1
[3, 2]
v1.modulus
3.605551275463989
type(v1)
__main__.vector
[d for d in dir(v1) if d.find('__')==-1]
['append',
'clear',
'copy',
'count',
'extend',
'index',
'insert',
'modulus',
'pop',
'remove',
'reverse',
'sort']
[d for d in dir(butterfly) if d.find('__')==-1]
['anntenaes',
'bones',
'eyes',
'legs',
'live',
'sex',
'stage',
'stings',
'wings']
Una implementación completa de la clase vector, adapta de vector: a list based vector class supporting elementwise operations (python recipe), se puede encontrar aquí
9.4. Otro ejemplo#
class veterinaria:
def __init__(self,x):
self.tipo={'perro':'guau','gato':'miau'}
self.sonido=self.tipo.get(x)
def __call__(self,nombre):
if nombre=='greco':
print(self.sonido)
else:
print("grrr!!")
def color(self,nombre):
if nombre=='greco':
print('blanco')
Cundo se inicializa la clase la función llamada
__init__
, se ejecuta automáticamente.La función
__call__
permite usar el objeto directamente como función, sin hacer referencia al método, que por supuesto es__call__
Hay muchos otros métodos especiales, que comienzan con un __
…. Usar <TAB>
a continuación para ver algunos
veterinaria.__
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/tmp/ipykernel_664677/3273899342.py in <module>
----> 1 veterinaria.__
AttributeError: type object 'veterinaria' has no attribute '__'
Para utilizar la clase, primero se debe incializar como un objeto. Crear la instancia de la clase.
Ejemplo: Crea la instancia mascotafeliz
mascotafeliz=veterinaria('perro')
mascotafeliz.sonido
'guau'
mascotafeliz('greco')
guau
mascotafeliz.color('greco')
blanco
mascotafeliz.tipo.get('vaca')
9.5. Herencia#
import pandas as pd
class finanzas(pd.DataFrame):
pass
f=finanzas()
type(f)
__main__.finanzas
f([{'A':1}])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_664677/2358335894.py in <module>
----> 1 f([{'A':1}])
TypeError: 'finanzas' object is not callable
Para realmente heradar hay que inicializarlo de forma especial con la función super
class hkdict(dict):
def __init__(self,*args, **kwargs):
super(hkdict, self).__init__(*args, **kwargs)
def has_key(self,k):
return k in self
d=dict()
d['perro']='guau'
d['gato']='miau'
d.get('vaca')
d.has_key('gato')
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/tmp/ipykernel_664677/1012315857.py in <module>
----> 1 d.has_key('gato')
AttributeError: 'dict' object has no attribute 'has_key'
dd=hkdict()
dd['perro']='guau'
dd['gato']='miau'
dd.has_key('vaca')
False
9.6. Implementation of arXiv:1905.13729#
See also: DOI: 10.1103/PhysRevD.101.095032
Repo: https://github.com/restrepo/anomalies
9.6.1. General solution to the U(1) anomaly equations#
Let a vector \(\boldsymbol{z}\) with \(N\) integer entries such that $\( \sum_{i=1}^N z_i=0\,,\qquad \sum_{i=1}^N z_i^3=0\,.\)\( We like to build this set of of \)N\( from two subsets \)\boldsymbol{\ell}\( and \)\boldsymbol{k}$ with sizes
\(N\) even: Consider the following two examples of \(\boldsymbol{z}\) with vector-like solutions, i.e, with opposite integeres which automatically satisfy the equations
\(N\) odd: Consider the two vector-like solutions
From any of this, we can build a final \(\boldsymbol{z}\) which can includes chiral solutions, i.e, non vector-like solutions
import numpy as np
class free(list):
def __init__(self,*args, **kwargs):
'''
Convert list to anomaly free solution
'''
assert np.array(args).sum()==0
assert (np.array(args)**3).sum()==0
super(free, self).__init__(*args, **kwargs)
def __add__(self, other):
'''
Add to anomaly free solutions to obtain
a new anomaly free solutions which is not
necessarily vector-like
'''
x=np.array(self)
y=np.array(other)
return free((x*y**2).sum()*x-(x**2*y).sum()*y)
def _z(l,k,sort=True,reverse=False):
'''
Implementation of arXiv:1905.13729
For l,k two set of same dimensions (or k with an extra dimension)
return a builded array z, such that
sum( z )=0
sum( z**3)=0
'''
l=list(l)
k=list(k)
#Build vector-like solutions x,y
if len(l)==len(k) :
x=free( [l[0]]+k+[-l[0]]+[-i for i in k ] )
y=free( [0,0] +l +[-i for i in l ] )
else:
x=free( [0]+k+[-i for i in k ] )
y=free( l+[k[0]]+[0]+[-i for i in l ]+[-k[0]])
xfac=0
yfac=0
## Build not trivial solution
zz=x+y
if sort:
zz=sorted( zz ,key=abs, reverse=reverse )
return zz
class solution(free):
def __init__(self,l,k,sort=True,reverse=False,**kwargs):
zz=_z(l,k,sort=sort,reverse=reverse)
self.gcd=np.gcd.reduce(zz)
self.simplified=free((zz/self.gcd).astype(int))
super(solution, self).__init__(zz, **kwargs)
def asarray(self):
self.simplified=np.asarray(self.simplified)
return np.asarray(self)
def to_list(self):
self.simplified=list(self.simplified)
return list(self)
free([-1, 4, -2, 1, -4, 2])
[-1, 4, -2, 1, -4, 2]
x=solution([-1,1],[4,-2])
type(x)
__main__.solution
x
[3, 3, 3, -12, -12, 15]
x.gcd
3
x.simplified
[1, 1, 1, -4, -4, 5]
type(x.simplified)
__main__.free
(np.asarray(x.simplified)**3).sum()
0
x.asarray()
array([ 3, 3, 3, -12, -12, 15])
x.to_list()
[3, 3, 3, -12, -12, 15]
y=solution([1,1],[4,-2])
y
[-5, 5, -20, 20, 25, -25]
z=x.simplified+y.simplified
z
[1, 1, 1, -4, -4, 5, -1, 1, -4, 4, 5, -5]
np.asarray(z).sum()
0
(np.asarray(z)**3).sum()
0
9.6.2. Python implmentation#
Obtain a numpy array z
of N
integers which satisfy the Diophantine equations
>>> z.sum()
0
>>> (z**3).sum()
0
The input is two lists l
and k
with any (N-3)/2
and (N-1)/2
integers for N
odd, or N/2-1
and N/2-1
for N
even (N>4
).
The function is implemented below under the name: free(l,k)
https://stackoverflow.com/a/43793179/2268280
import pandas as pd
import numpy as np
from astropy.table import Table
import itertools
import sys
import os
from functools import reduce
import warnings
warnings.filterwarnings("ignore")
!pip install anomalies 2>/dev/null > /dev/null
from anomalies import anomaly
anomaly.free([-1,1],[4,-2])
array([ 3, 3, 3, -12, -12, 15])
anomaly.free.gcd
3
anomaly.free.simplified
array([ 1, 1, 1, -4, -4, 5])
z=anomaly.free
9.6.3. Analysis#
solutions
class → Initialize the object to obtain anomaly free solutions for any set of N
integers
#TODO: inherit from free class
import sys
def _get_chiral(q,q_max=np.inf):
#Normalize to positive minimum
if q[0]<0 or (q[0]==0 and q[1]<0):
q=-q
#Divide by GCD
GCD=np.gcd.reduce(q)
q=(q/GCD).astype(int)
if ( #not 0 in z and
0 not in [ sum(p) for p in itertools.permutations(q, 2) ] and #avoid vector-like and multiple 0's
#q.size > np.unique(q).size and ## check for at least a duplicated entry
np.abs(q).max()<=q_max
):
return q,GCD
else:
return None,None
class solutions(object):
'''
Obtain anomaly free solutions with N chiral fields
Call the initialize object with N and get the solutions:
Example:
>>> s=solutions()
>>> s(6) ## N = 6
Redefine the self.chiral function to implement further restrictions:
inherit from this class and define the new chiral function
'''
def __init__(self,nmin=-2,nmax=2,zmax=np.inf):
self.nmin=nmin
self.nmax=nmax
self.zmax=zmax
self.CALL=False
def __call__(self,N,*args,**kwargs):
self.CALL=True
if N%2!=0: #odd
N_l=(N-3)//2
N_k=(N-1)//2
else: #even
N_l=N//2-1
N_k=N_l
r=range(self.nmin,self.nmax+1)
self.ls=list(itertools.product( *(r for i in range(N_l)) ))
self.ks=list(itertools.product( *(r for i in range(N_k)) ))
return self.chiral(*args,**kwargs)
def chiral(self,*args,**kwargs):
if not self.CALL:
sys.exit('Call the initialized object first:\n>>> s=solutions()\n>>> self(5)')
self.list=[]
solt=[]
for l in self.ls:
for k in self.ks:
l=list(l)
k=list(k)
q,gcd=_get_chiral( z(l,k) )
#print(z(l,k))
if q is not None and list(q) not in self.list and list(-q) not in self.list:
self.list.append(list(q))
solt.append({'l':l,'k':k,'z':list(q),'gcd':gcd})
return solt
Chiral solutions for l and k in the range [-2,2]
s=solutions()
solutions for \(N=5\) integers
s(5)
[{'l': [-2], 'k': [-1, 2], 'z': [2, 4, -7, -9, 10], 'gcd': 1},
{'l': [-2], 'k': [2, -1], 'z': [1, 5, -7, -8, 9], 'gcd': 4}]
pd.DataFrame( s(5) )
l | k | z | gcd | |
---|---|---|---|---|
0 | [-2] | [-1, 2] | [2, 4, -7, -9, 10] | 1 |
1 | [-2] | [2, -1] | [1, 5, -7, -8, 9] | 4 |
To filter solutions with duplicate or triplicate integers, let us create a class dark
that inherits from solutions
. Therefore, in the argument of the new class is the old class instead of just object
class dark(solutions):
'''
Modify the self.chiral function to obtain solutions
with either duplicate or triplicate integers
'''
def chiral(self,X=False,verbose=False,print_step=100000):
m=2
if X:
m=3
self.list=[]
solt=[]
tot=len(self.ls)*len(self.ks)
i=0
for l in self.ls:
for k in self.ks:
if verbose:
i=i+1
if i%print_step==0:
print('{}/{}'.format(i,tot))
l=list(l)
k=list(k)
q,gcd=_get_chiral( z(l,k) )
#print(z(l,k))
if (q is not None and
list(q) not in self.list and list(-q) not in self.list and
1 in [ len(set(p)) for p in itertools.permutations(q, m) ] and
#q.size-np.unique(q).size>m
np.abs(q).max()<=self.zmax
):
self.list.append(list(q))
solt.append({'l':l,'k':k,'z':list(q),'gcd':gcd})
return solt
Chiral solutions with repeated integers
s=dark()
Example: Force solutions with triplicate integers
s(5)
[]
pd.DataFrame( s(6,X=True) )
l | k | z | gcd | |
---|---|---|---|---|
0 | [-2, 2] | [-2, -1] | [1, 1, 1, -4, -4, 5] | 8 |
%%time
s=dark(nmin=-30,nmax=30)
s(5)
CPU times: user 16.5 s, sys: 7.15 ms, total: 16.5 s
Wall time: 16.6 s
[]
%%time
s=dark(nmin=-10,nmax=10,zmax=32)
s(6,X=True,verbose=True)
100000/194481
CPU times: user 19.9 s, sys: 0 ns, total: 19.9 s
Wall time: 19.9 s
[{'l': [-10, 5], 'k': [-2, 4], 'z': [1, 1, 1, -4, -4, 5], 'gcd': 1500},
{'l': [-10, 5], 'k': [3, 4], 'z': [3, 3, 3, -10, -17, 18], 'gcd': 250}]
%%time
s=dark(nmin=-21,nmax=21,zmax=32)
slt=s(6,verbose=True,print_step=500000)
500000/3418801
1000000/3418801
1500000/3418801
2000000/3418801
2500000/3418801
3000000/3418801
CPU times: user 4min 20s, sys: 27.1 ms, total: 4min 20s
Wall time: 4min 20s
9.6.4. Example#
Simple csv to json converter
import csv as CSV
class read_csv:
def __init__(self,f):
self.data=[]
f=open(f,'r')
dt=CSV.reader(f)
for row in dt:
self.data.append(row)
f.close()
def to_json(self):
return [dict(zip(data[0],d)) for d in self.data[1:]]
9.7. Decorating classes#
From https://realpython.com/primer-on-python-decorators/#decorating-classes
Some commonly used decorators that are even built-ins in Python are
@classmethod
,@staticmethod
, and@property
. The@classmethod
and@staticmethod
decorators are used to define methods inside a class namespace that are not connected to a particular instance of that class. The@property
decorator is used to customize getters and setters for class attributes.
9.8. References#
[1] A Guide to Python’s Magic Methods
[2] https://realpython.com/python3-object-oriented-programming/
[3] Building Skills in Object-Oriented Design
[4] Ver también: https://gist.github.com/mcleonard/5351452
[5] Bhasin, Harsh. Python Basics: A Self-teaching Introduction. Stylus Publishing, LLC, 2018. [PDF] [Google Scholar]