Famílias, Conversão e Coação - Tutorial Sage

Skip to content
Back to top
View this page
Edit this page

# 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

    sage: P.<x,y> = GF(3)[]
    sage: Q.<a,b> = GF(4,'z')[]
    sage: type(x)==type(a)
    True

Python

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

    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

[Python]

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

    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)

Python

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

    sage: isinstance(QQ,Field)
    True
    sage: isinstance(QQ, Ring)
    True
    sage: isinstance(ZZ,Field)
    False
    sage: isinstance(ZZ, Ring)
    True

Python

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

    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

[Python]

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

    sage: RR['x','y'] is RR['x','y']
    True

Python

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

    sage: cristovao = ZZ(1492)
    sage: isinstance(cristovao, RingElement)
    True

Python

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

    sage: int(1) is int(1) # Python int
    True
    sage: int(-15) is int(-15)
    False
    sage: 1 is 1           # Sage Integer
    False

[Python]

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

    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

Python

    >>> 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 aner `R1` pode de alguma forma ser interpretado em um outro anel `R2`, então todas as operações aritméticas envolvendo `r1` e qualquer elemento de `R2` 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

    sage: a = GF(2)(1)
    sage: b = GF(5)(1)
    sage: GF(5)(a) == b
    True
    sage: GF(2)(b) == a
    True

Python

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

1. Uma coação de `P1` para `P2` deve ser dada por uma estrutura que preserva mapeamentos (por exemplo, um homomorfismo de anéis). Não é suficiente que alguns elementos de `P1` possam ser mapeados em `P2`, e o mapa deve respeitar a estrutura algébrica de `P1`.

2. 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 de `P1` para `P2` com a coação de `P2` para `P3` deve coincidir com a coação adotada de `P1` para `P3`. Em particular, se existir uma coação de `P1` para `P2` e `P2` para `P1`, a composição deve ser o mapa identidade em `P1`.

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

    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
    sage: R2.coerce(y)
    y

[Python]

    >>> 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
    >>> R2.coerce(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

    sage: R3 = ZZ['z','x']
    sage: R3.has_coerce_map_from(R1)
    False
    sage: R3(x)
    z
    sage: R3(y)
    x
    sage: R3.coerce(y)
    Traceback (most recent call last):
    ...
    TypeError: no canonical coercion
    from Multivariate Polynomial Ring in x, y over Integer Ring
    to Multivariate Polynomial Ring in z, x over Integer Ring

Python

    >>> from sage.all import *
    >>> R3 = ZZ['z','x']
    >>> R3.has_coerce_map_from(R1)
    False
    >>> R3(x)
    z
    >>> R3(y)
    x
    >>> R3.coerce(y)
    Traceback (most recent call last):
    ...
    TypeError: no canonical coercion
    from Multivariate Polynomial Ring in x, y over Integer Ring
    to Multivariate Polynomial Ring in z, x over Integer Ring

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

    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

[Python]

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

    sage: R3(R1.1) == R3.1
    True
    sage: R1.1 == R3.1
    False
    sage: R1.1 != R3.1
    True

Python

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

    sage: RR(1/10^200+1/10^100) == RR(1/10^100)
    True
    sage: 1/10^200+1/10^100 == 1/10^100
    False

[Python]

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

    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

Python

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

    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'

[Python]

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