Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
restrepo
GitHub Repository: restrepo/ComputationalMethods
Path: blob/master/material/Intro_clases.ipynb
934 views
Kernel: Python 3 (ipykernel)

Open In Colab

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: Enteros

  • float: Números punto flotante (floating point numbers)

  • str: Cadenas de caracteres

  • list: Listas

  • dict: 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)\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:

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']

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 clase

  • int() es la instancia de la clase

  • n=int() es el objeto asociado a la clase: n.→ contiene todos los atributos y los métodos de la clase int

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
df=pd.DataFrame({'A':[1],'B':[2]}) df

Programación por clases

Una clase se puede pensar como un conjuto de funciones y atributos que comparten algo en común. Por ejemplo, los miembros de una familia

class names: pass Uribe = names() Uribe.Vélez = names() Uribe.Vélez.Álvaro = 'father' Moreno.de = names() Moreno.de.Uribe = names() Moreno.de.Uribe.Lina = names() Moreno.de.Uribe.Lina.María = 'mother' Uribe.Moreno = names () Uribe.Moreno.Tomás = 'first son' Uribe.Moreno.Jerónimo = 'second son' # 1) Define father's sister: María Isabel # .... # 2) Check an instance of the object: # Uribe.<TAB>

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 selfc. 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'

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

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'

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:

super() gives you access to methods in a superclass from the subclass that inherits from it.

super() is the same as super(__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

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í

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')

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

Implementation of arXiv:1905.13729

See also: DOI: 10.1103/PhysRevD.101.095032

Repo: https://github.com/restrepo/anomalies

General solution to the U(1) anomaly equations

Let a vector z\boldsymbol{z} with NN integer entries such that i=1Nzi=0,i=1Nzi3=0. \sum_{i=1}^N z_i=0\,,\qquad \sum_{i=1}^N z_i^3=0\,. We like to build this set of of NN from two subsets \boldsymbol{\ell} and k\boldsymbol{k} with sizes

dim()={α=N21, if N even β=N32, if N odd ;dim(k)={α=N21, if N even β+1=N12, if N odd ;\begin{align} \operatorname{dim}(\boldsymbol{\ell})=& \begin{cases} \alpha=\frac{N}{2}-1\,, & \text{ if $N$ even } \\ \beta=\frac{N-3}{2}\,, & \text{ if $N$ odd }\\ \end{cases};& \operatorname{dim}(\boldsymbol{k})=& \begin{cases} \alpha=\frac{N}{2}-1\,, & \text{ if $N$ even } \\ \beta+1=\frac{N-1}{2}\,, & \text{ if $N$ odd }\\ \end{cases}; \end{align}
  • NN even: Consider the following two examples of z\boldsymbol{z} with vector-like solutions, i.e, with opposite integeres which automatically satisfy the equations x=(1,k1,,kα,1,k1,,kα)y=(0,0,1,,α,1,,α).\begin{align} \boldsymbol{x}=&\left(\ell_{1}, {k}_{1}, \ldots, k_{\alpha},-\ell_{1},-k_{1}, \ldots,-k_{\alpha}\right)\\ \boldsymbol{y}=&\left(0,0,\ell_{1}, \ldots, \ell_{\alpha},-\ell_1, \ldots,-\ell_{\alpha}\right)\,. \end{align}

  • NN odd: Consider the two vector-like solutions ParseError: KaTeX parse error: Unexpected end of input in a macro argument, expected '}' at position 29: … \begin{array} \̲b̲o̲l̲d̲s̲y̲m̲b̲o̲l̲{x}=&\left(0, k…

From any of this, we can build a final z\boldsymbol{z} which can includes chiral solutions, i.e, non vector-like solutions

xy(i=1Nxiyi2)x(i=1Nxi2yi)y.\boldsymbol{x} \oplus \boldsymbol{y} \equiv\left(\sum_{i=1}^{N} x_{i} y_{i}^{2}\right)\boldsymbol{x}-\left(\sum_{i=1}^{N} x_{i}^{2} y_{i}\right)\boldsymbol{y}\,.
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

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)

Open In Colab

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

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=5N=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) )

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) )
%%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

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:]]

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.

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]