Famílias, Conversão e Coação#
Esta seção pode parecer mais técnica do que as anteriores, mas acreditamos que é importante entender o significado de famílias e coação de modo a usar anéis e outras estruturas algébricas no Sage de forma efetiva e eficiente.
Note que vamos explicar algumas noções, mas não vamos mostrar aqui como implementá-las. Um tutorial voltado à implementação está disponível (em inglês) como um tutorial temática.
Elementos#
Caso se queira implementar um anel em Python, uma primeira aproximação
seria criar uma classe para os elementos X
do anel e adicionar os
requeridos métodos (com underscores duplos) __add__
, __sub
,
__mul__
, obviamente garantindo que os axiomas de anel são
verificados.
Como o Python é uma linguagem de tipagem forte (ainda que de tipagem
dinâmica), poderia-se, pelo menos a princípio, esperar-se que fosse
implementado em Python uma classe para cada anel. No final das contas,
o Python contém um tipo <int>
para os inteiros, um tipo
<float>
para os reais, e assim por diante. Mas essa estratégia
logo encontra uma limitação: Existe um número infinito de anéis, e não
se pode implementar um número infinito de classes.
Em vez disso, poderia-se criar uma hierarquia de classes projetada para implementar elementos de estruturas algébricas ubíquas, tais como grupos, anéis, anéis comutativos, corpos, álgebras, e assim por diante.
Mas isso significa que elementos de anéis bastante diferentes podem ter o mesmo tipo.
sage: P.<x,y> = GF(3)[]
sage: Q.<a,b> = GF(4,'z')[]
sage: type(x)==type(a)
True
>>> from sage.all import *
>>> P = GF(Integer(3))['x, y']; (x, y,) = P._first_ngens(2)
>>> Q = GF(Integer(4),'z')['a, b']; (a, b,) = Q._first_ngens(2)
>>> type(x)==type(a)
True
Por outro lado, poderia-se ter também classes diferentes em Python fornecendo implementações diferentes da mesma estrutura matemática (por exemplo, matrizes densas versus matrizes esparsas).
sage: P.<a> = PolynomialRing(ZZ)
sage: Q.<b> = PolynomialRing(ZZ, sparse=True)
sage: R.<c> = PolynomialRing(ZZ, implementation='NTL')
sage: type(a) == type(b)
False
sage: type(a) == type(c)
False
sage: type(b) == type(c)
False
>>> from sage.all import *
>>> P = PolynomialRing(ZZ, names=('a',)); (a,) = P._first_ngens(1)
>>> Q = PolynomialRing(ZZ, sparse=True, names=('b',)); (b,) = Q._first_ngens(1)
>>> R = PolynomialRing(ZZ, implementation='NTL', names=('c',)); (c,) = R._first_ngens(1)
>>> type(a) == type(b)
False
>>> type(a) == type(c)
False
>>> type(b) == type(c)
False
Isso apresenta dois problemas: Por um lado, se tivéssemos elementos
que são duas instancias da mesma classe, então poderia-se esperar que
o método __add__
dessas classes permitisse somá-los; mas não
se deseja isso, se os elementos pertencem a anéis bastante diferentes.
Por outro lado, se possui-se elementos que pertencem a implementações
diferentes do mesmo anel, então gostaria-se de somá-los, mas isso não
pode ser feito diretamente se eles pertencem a classes diferentes em
Python.
A solução para esses problemas é chamada coação e será explicada a seguir.
Todavia, é essencial que cada elemento saiba a qual pertence. Isso
está disponível através método parent()
:
sage: a.parent(); b.parent(); c.parent()
Univariate Polynomial Ring in a over Integer Ring
Sparse Univariate Polynomial Ring in b over Integer Ring
Univariate Polynomial Ring in c over Integer Ring (using NTL)
>>> from sage.all import *
>>> a.parent(); b.parent(); c.parent()
Univariate Polynomial Ring in a over Integer Ring
Sparse Univariate Polynomial Ring in b over Integer Ring
Univariate Polynomial Ring in c over Integer Ring (using NTL)
Famílias e Categorias#
De forma similar à hierarquia de classes em Python voltada para elementos de estruturas algébricas, o Sage também fornece classes para as estruturas algébricas que contém esses elementos. Estruturas contendo elementos são chamadas “estruturas parente” no Sage, e existe uma classe básica para elas. Paralelamente à hierarquia de noções matemáticas, tem-se uma hierarquia de classes, a saber, para conjuntos, anéis, corpos e assim por diante:
sage: isinstance(QQ,Field)
True
sage: isinstance(QQ, Ring)
True
sage: isinstance(ZZ,Field)
False
sage: isinstance(ZZ, Ring)
True
>>> from sage.all import *
>>> isinstance(QQ,Field)
True
>>> isinstance(QQ, Ring)
True
>>> isinstance(ZZ,Field)
False
>>> isinstance(ZZ, Ring)
True
Em álgebra, objetos que compartilham o mesmo tipo de estruturas algébricas são agrupados nas assim chamadas “categorias”. Logo, existe uma analogia aproximada entre a hierarquia de classes em Sage e a hierarquia de categorias. Todavia, essa analogia de classes em Python e categorias não deve ser enfatizada demais. No final das contas, categorias matemáticas também são implementadas no Sage:
sage: Rings()
Category of rings
sage: ZZ.category()
Join of Category of Dedekind domains
and Category of euclidean domains
and Category of noetherian rings
and Category of infinite enumerated sets
and Category of metric spaces
sage: ZZ.category().is_subcategory(Rings())
True
sage: ZZ in Rings()
True
sage: ZZ in Fields()
False
sage: QQ in Fields()
True
>>> from sage.all import *
>>> Rings()
Category of rings
>>> ZZ.category()
Join of Category of Dedekind domains
and Category of euclidean domains
and Category of noetherian rings
and Category of infinite enumerated sets
and Category of metric spaces
>>> ZZ.category().is_subcategory(Rings())
True
>>> ZZ in Rings()
True
>>> ZZ in Fields()
False
>>> QQ in Fields()
True
Enquanto a hierarquia de classes no Sage é centrada nos detalhes de implementação, a construção de categorias em Sage é mais centrada na estrutura matemática. É possível implementar métodos e testes gerais independentemente de uma implementação específica nas categorias.
Estruturas da mesma família em Sage são supostamente objetos únicos em Python. Por exemplo, uma vez que um anel de polinômios sobre um certo anel base e com uma certa lista de geradores é criada, o resultado é arquivado:
sage: RR['x','y'] is RR['x','y']
True
>>> from sage.all import *
>>> RR['x','y'] is RR['x','y']
True
Tipos versus Parentes#
O tipo RingElement
não deve ser confundido com a noção matemática
de elemento de anel; por razões práticas, as vezes um objeto é uma
instancia de RingElement
embora ele não pertence a um anel:
sage: cristovao = ZZ(1492)
sage: isinstance(cristovao, RingElement)
True
>>> from sage.all import *
>>> cristovao = ZZ(Integer(1492))
>>> isinstance(cristovao, RingElement)
True
Enquanto famílias são únicas, elementos iguais de uma família em Sage não são necessariamente idênticos. Isso contrasta com o comportamento do Python para alguns (embora não todos) inteiros:
sage: int(1) is int(1) # Python int
True
sage: int(-15) is int(-15)
False
sage: 1 is 1 # Sage Integer
False
>>> from sage.all import *
>>> int(Integer(1)) is int(Integer(1)) # Python int
True
>>> int(-Integer(15)) is int(-Integer(15))
False
>>> Integer(1) is Integer(1) # Sage Integer
False
É importante observar que elementos de anéis diferentes em geral não podem ser distinguidos pelos seus tipos, mas sim por sua família:
sage: a = GF(2)(1)
sage: b = GF(5)(1)
sage: type(a) is type(b)
True
sage: parent(a)
Finite Field of size 2
sage: parent(b)
Finite Field of size 5
>>> from sage.all import *
>>> a = GF(Integer(2))(Integer(1))
>>> b = GF(Integer(5))(Integer(1))
>>> type(a) is type(b)
True
>>> parent(a)
Finite Field of size 2
>>> parent(b)
Finite Field of size 5
Logo, de um ponto de vista algébrico, o parente de um elemento é mais importante do que seu tipo.
Conversão versus Coação#
Em alguns casos é possível converter um elemento de uma estrutura parente em um elemento de uma outra estrutura parente. Tal conversão pode ser tanto explícita como implícita (essa é chamada coação).
O leitor pode conhecer as noções de conversão de tipo e coação de tipo como na linguagem C, por exemplo. Existem noções de conversão e coação em Sage também. Mas as noções em Sage são centradas em família, não em tipos. Então, por favor não confunda conversão de tipo em C com conversão em Sage!
Aqui se encontra uma breve apresentação. Para uma descrição detalhada e informações sobre a implementação, referimos à seção sobre coação no manual de referência e para o tutorial.
Existem duas possibilidades extremas com respeito à possibilidade de fazer aritmética com elementos de anéis diferentes:
Anéis diferentes são mundos diferentes, e não faz nenhum sentido somar ou multiplicar elementos de anéis diferentes; mesmo
1 + 1/2
não faz sentido, pois o primeiro somando é um inteiro e o segundo um racional.
Ou
Se um elemento
r1
de uma anerR1
pode de alguma forma ser interpretado em um outro anelR2
, então todas as operações aritméticas envolvendor1
e qualquer elemento deR2
são permitidas. O elemento neutro da multiplicação existe em todos os corpos e em vários anéis, e eles devem ser todos iguais.
O Sage faz uma concessão. Se P1
e P2
são estruturas da mesma família
e p1
é um elemento de P1
, então o usuário pode explicitamente
perguntar por uma interpretação de p1
em P2
. Isso pode não fazer
sentido em todos os casos ou não estar definido para todos os elementos de
P1
, e fica a cargo do usuário assegurar que isso faz sentido. Nos
referimos a isso como conversão:
sage: a = GF(2)(1)
sage: b = GF(5)(1)
sage: GF(5)(a) == b
True
sage: GF(2)(b) == a
True
>>> from sage.all import *
>>> a = GF(Integer(2))(Integer(1))
>>> b = GF(Integer(5))(Integer(1))
>>> GF(Integer(5))(a) == b
True
>>> GF(Integer(2))(b) == a
True
Todavia, uma conversão implícita (ou automática) ocorrerá apenas se puder ser feita completamente e consistentemente. Rigor matemático é essencial nesse ponto.
Uma tal conversão implícita é chamada coação. Se coação for definida, então deve coincidir com conversão. Duas condições devem ser satisfeitas para uma coação ser definida:
Uma coação de
P1
paraP2
deve ser dada por uma estrutura que preserva mapeamentos (por exemplo, um homomorfismo de anéis). Não é suficiente que alguns elementos deP1
possam ser mapeados emP2
, e o mapa deve respeitar a estrutura algébrica deP1
.A escolha desses mapas de coação deve ser consistente: Se
P3
é uma terceira estrutura parente, então a composição da coação adotada deP1
paraP2
com a coação deP2
paraP3
deve coincidir com a coação adotada deP1
paraP3
. Em particular, se existir uma coação deP1
paraP2
eP2
paraP1
, a composição deve ser o mapa identidade emP1
.
Logo, embora é possível converter cada elemento de GF(2)
para
GF(5)
, não há coação, pois não existe homomorfismo de anel entre
GF(2)
e GF(5)
.
O segundo aspecto - consistência - é um pouco mais difícil de explicar. Vamos ilustrá-lo usando anéis de polinômios em mais de uma variável. Em aplicações, certamente faz mais sentido ter coações que preservam nomes. Então temos:
sage: R1.<x,y> = ZZ[]
sage: R2 = ZZ['y','x']
sage: R2.has_coerce_map_from(R1)
True
sage: R2(x)
x
sage: R2(y)
y
>>> from sage.all import *
>>> R1 = ZZ['x, y']; (x, y,) = R1._first_ngens(2)
>>> R2 = ZZ['y','x']
>>> R2.has_coerce_map_from(R1)
True
>>> R2(x)
x
>>> R2(y)
y
Se não existir homomorfismo de anel que preserve nomes, coação não é definida. Todavia, conversão pode ainda ser possível, a saber, mapeando geradores de anel de acordo com sua posição da lista de geradores:
sage: R3 = ZZ['z','x']
sage: R3.has_coerce_map_from(R1)
False
sage: R3(x)
z
sage: R3(y)
x
>>> from sage.all import *
>>> R3 = ZZ['z','x']
>>> R3.has_coerce_map_from(R1)
False
>>> R3(x)
z
>>> R3(y)
x
Mas essas conversões que preservam a posição não se qualificam como
coação: Compondo um mapa que preserva nomes de ZZ['x','y']
para
ZZ['y','x']
, com um mapa que preserva nomes de ZZ['y','x']
para ZZ['a','b']
, resultaria em um mapa que não preserva nomes nem
posição, violando a consistência.
Se houver coação, ela será usada para comparar elementos de anéis
diferentes ou fazer aritmética. Isso é frequentemente conveniente, mas
o usuário deve estar ciente que estender a relação ==
além das
fronteiras de famílias diferentes pode facilmente resultar em
problemas. Por exemplo, enquanto ==
é supostamente uma relação de
equivalência sobre os elementos de um anel, isso não é
necessariamente o caso se anéis diferentes estão envolvidos. Por
exemplo, 1
em ZZ
e em um corpo finito são considerados iguais,
pois existe uma coação canônica dos inteiros em qualquer corpo finito.
Todavia, em geral não existe coação entre dois corpos finitos
diferentes. Portanto temos
sage: GF(5)(1) == 1
True
sage: 1 == GF(2)(1)
True
sage: GF(5)(1) == GF(2)(1)
False
sage: GF(5)(1) != GF(2)(1)
True
>>> from sage.all import *
>>> GF(Integer(5))(Integer(1)) == Integer(1)
True
>>> Integer(1) == GF(Integer(2))(Integer(1))
True
>>> GF(Integer(5))(Integer(1)) == GF(Integer(2))(Integer(1))
False
>>> GF(Integer(5))(Integer(1)) != GF(Integer(2))(Integer(1))
True
Similarmente,
sage: R3(R1.1) == R3.1
True
sage: R1.1 == R3.1
False
sage: R1.1 != R3.1
True
>>> from sage.all import *
>>> R3(R1.gen(1)) == R3.gen(1)
True
>>> R1.gen(1) == R3.gen(1)
False
>>> R1.gen(1) != R3.gen(1)
True
Uma outra consequência da condição de consistência é que coação pode
apenas ir de anéis exatos (por exemplo, os racionais QQ
) para
anéis não-exatos (por exemplo, os números reais com uma precisão fixa
RR
), mas não na outra direção. A razão é que a composição da
coação de QQ
em RR
com a conversão de RR
para QQ
deveria ser a identidade em QQ
. Mas isso é impossível, pois alguns
números racionais distintos podem ser tratados como iguais em RR
,
como no seguinte exemplo:
sage: RR(1/10^200+1/10^100) == RR(1/10^100)
True
sage: 1/10^200+1/10^100 == 1/10^100
False
>>> from sage.all import *
>>> RR(Integer(1)/Integer(10)**Integer(200)+Integer(1)/Integer(10)**Integer(100)) == RR(Integer(1)/Integer(10)**Integer(100))
True
>>> Integer(1)/Integer(10)**Integer(200)+Integer(1)/Integer(10)**Integer(100) == Integer(1)/Integer(10)**Integer(100)
False
Quando se compara elementos de duas famílias P1
e P2
, é
possível que não haja coação entre os dois anéis, mas existe uma
escolha canônica de um parente P3
de modo que tanto P1
como
P2
são coagidos em P3
. Nesse caso, coação vai ocorrer também.
Um caso de uso típico é na soma de um número racional com um polinômio
com coeficientes inteiros, resultando em um polinômio com coeficientes
racionais:
sage: P1.<x> = ZZ[]
sage: p = 2*x+3
sage: q = 1/2
sage: parent(p)
Univariate Polynomial Ring in x over Integer Ring
sage: parent(p+q)
Univariate Polynomial Ring in x over Rational Field
>>> from sage.all import *
>>> P1 = ZZ['x']; (x,) = P1._first_ngens(1)
>>> p = Integer(2)*x+Integer(3)
>>> q = Integer(1)/Integer(2)
>>> parent(p)
Univariate Polynomial Ring in x over Integer Ring
>>> parent(p+q)
Univariate Polynomial Ring in x over Rational Field
Note que a princípio o resultado deveria também fazer sentido no
corpo de frações de ZZ['x']
. Todavia, o Sage tenta escolher um
parente canônico comum que parece ser o mais natural (QQ['x']
no
nosso exemplo). Se várias famílias potencialmente comuns parecem
igualmente naturais, o Sage não vai escolher um deles
aleatoriamente. Os mecanismos sobre os quais essa escolha se baseia é
explicado em um
tutorial
Nenhuma coação para um parente comum vai ocorrer no seguinte exemplo:
sage: R.<x> = QQ[]
sage: S.<y> = QQ[]
sage: x+y
Traceback (most recent call last):
...
TypeError: unsupported operand parent(s) for +: 'Univariate Polynomial Ring in x over Rational Field' and 'Univariate Polynomial Ring in y over Rational Field'
>>> from sage.all import *
>>> R = QQ['x']; (x,) = R._first_ngens(1)
>>> S = QQ['y']; (y,) = S._first_ngens(1)
>>> x+y
Traceback (most recent call last):
...
TypeError: unsupported operand parent(s) for +: 'Univariate Polynomial Ring in x over Rational Field' and 'Univariate Polynomial Ring in y over Rational Field'
A razão é que o Sage não escolhe um dos potenciais candidatos
QQ['x']['y']
, QQ['y']['x']
, QQ['x','y']
ou
QQ['y','x']
, porque todas essas estruturas combinadas em pares
diferentes parecem ser de famílias comuns naturais, e não existe escolha
canônica aparente.