# Constructing mathematical objects in AbstractAlgebra.jl

## Constructing objects in Julia

In Julia, one constructs objects of a given type by calling a type constructor. This is simply a function with the same name as the type itself. For example, to construct a `BigInt`

object from an `Int`

in Julia, we simply call the `BigInt`

constructor:

`n = BigInt(123)`

Note that a number literal too big to fit in an `Int`

or `Int128`

automatically creates a `BigInt`

:

```
julia> typeof(12345678765456787654567890987654567898765678909876567890)
BigInt
```

## How we construct objects in AbstractAlgebra.jl

As we explain in Elements and parents, Julia types don't contain enough information to properly model groups, rings, fields, etc. Instead of using types to construct objects, we use special objects that we refer to as parent objects. They behave a lot like Julia types.

Consider the following simple example, to create a multiprecision integer:

`n = ZZ(12345678765456787654567890987654567898765678909876567890)`

Here `ZZ`

is not a Julia type, but a callable object. However, for most purposes one can think of such a parent object as though it were a type.

## Constructing parent objects

For more complicated groups, rings, fields, etc., one first needs to construct the parent object before one can use it to construct element objects.

AbstractAlgebra.jl provides a set of functions for constructing such parent objects. For example, to create a parent object for univariate polynomials over the integers, we use the `polynomial_ring`

parent object constructor.

```
R, x = polynomial_ring(ZZ, :x)
f = x^3 + 3x + 1
g = R(12)
```

In this example, `R`

is the parent object and we use it to convert the `Int`

value $12$ to an element of the polynomial ring $\mathbb{Z}[x]$.

## List of parent object constructors

For convenience, we provide a list of all the parent object constructors in AbstractAlgebra.jl and explain what mathematical domains they represent.

Mathematics | AbstractAlgebra.jl constructor |
---|---|

$R = \mathbb{Z}$ | `R = ZZ` |

$R = \mathbb{Q}$ | `R = QQ` |

$R = \mathbb{F}_{p}$ | `R = GF(p)` |

$R = \mathbb{Z}/n\mathbb{Z}$ | `R, = residue_ring(ZZ, n)` |

$S = R[x]$ | `S, x = polynomial_ring(R, :x)` |

$S = R[x, y]$ | `S, (x, y) = polynomial_ring(R, [:x, :y])` |

$S = R\langle x, y\rangle$ | `S, (x, y) = free_associative_algebra(R, [:x, :y])` |

$S = K(x)$ | `S, x = rational_function_field(K, :x)` |

$S = K(x, y)$ | `S, (x, y) = rational_function_field(K, [:x, :y])` |

$S = R[[x]]$ (to precision $n$) | `S, x = power_series_ring(R, n, :x)` |

$S = R[[x, y]]$ (to precision $n$) | `S, (x, y) = power_series_ring(R, n, [:x, :y])` |

$S = R((x))$ (to precision $n$) | `S, x = laurent_series_ring(R, n, :x)` |

$S = K((x))$ (to precision $n$) | `S, x = laurent_series_field(K, n, :x)` |

$S = R((x, y))$ (to precision $n$) | `S, (x, y) = laurent_polynomial_ring(R, n, [:x, :y])` |

Puiseux series ring to precision $n$ | `S, x = puiseux_series_ring(R, n, :x)` |

Puiseux series field to precision $n$ | `S, x = puiseux_series_field(R, n, :x)` |

$S = K(x)(y)/(f)$ | `S, y = function_field(f, :y)` with $f\in K(x)[t]$ |

$S = \mathrm{Frac}_R$ | `S = fraction_field(R)` |

$S = R/(f)$ | `S, = residue_ring(R, f)` |

$S = R/(f)$ (with $(f)$ maximal) | `S, = residue_field(R, f)` |

$S = \mathrm{Mat}_{m\times n}(R)$ | `S = matrix_space(R, m, n)` |

## Parent objects with variable names

The multivariate parent object constructors (`polynomial_ring`

, `power_series_ring`

, `free_associative_algebra`

, `laurent_polynomial_ring`

, and `rational_function_field`

) share a common interface for specifying the variable names, which is provided by `@varnames_interface`

.

`AbstractAlgebra.@varnames_interface`

— Macro`@varnames_interface [M.]f(args..., varnames) macros=:yes n=n range=1:n`

Add methods `X, vars = f(args..., varnames...)`

and macro `X = @f(args..., varnames...`

) to current scope.

**Created methods**

`X, gens::Vector{T} = f(args..., varnames::Vector{Symbol})`

Base method, called by everything else defined below. If a module `M`

is specified, this is implemented as a call to `M.f`

. Otherwise, a method `f`

with this signature must already exist.

```
X, gens... = f(args..., varnames...; kv...)
X, gens... = f(args..., varnames::Tuple; kv...)
```

Compute `X`

and `gens`

via the base method. Then reshape `gens`

into the shape defined by `varnames`

according to `variable_names`

.

The vararg `varnames...`

method needs at least one argument to avoid confusion. Moreover a single `VarName`

argument will be dispatched to use a univariate method of `f`

if it exists (e.g. `polynomial_ring(R, :x)`

). If you need those cases, use the `Tuple`

method.

Keyword arguments are passed on to the base method.

`X, x::Vector{T} = f(args..., n::Int, s::VarName = :x; kv...)`

Shorthand for `X, x = f(args..., "$s#" => 1:n; kv...)`

. The name of the argument `n`

can be changed via the `n`

option. The range `1:n`

is given via the `range`

option.

Setting `n=:no`

disables creation of this method.

```
X = @f(args..., varnames...; kv...)
X = @f(args..., varnames::Tuple; kv...)
X = @f(args..., varname::VarName; kv...)
```

These macros behave like their `f(args..., varnames; kv...)`

counterparts but also introduce the indexed `varnames`

into the current scope. The first version needs at least one `varnames`

argument. The third version calls the univariate base method if it exists (e.g. `polynomial_ring(R, varname)`

).

Setting `macros=:no`

disables macro creation.

Turning `varnames`

into a vector of symbols happens by evaluating `variable_names(varnames)`

in the global scope of the current module. For interactive usage in the REPL this is fine, but in general you have no access to local variables and should not use any side effects in `varnames`

.

**Examples**

```
julia> f(a, s::Vector{Symbol}) = a, String.(s)
f (generic function with 1 method)
julia> AbstractAlgebra.@varnames_interface f(a, s)
@f (macro with 1 method)
julia> f
f (generic function with 5 methods)
julia> f("hello", [:x, :y, :z])
("hello", ["x", "y", "z"])
julia> f("hello", :x => (1:1, 1:2), :y => 1:2, [:z])
("hello", ["x[1, 1]" "x[1, 2]"], ["y[1]", "y[2]"], ["z"])
julia> f("projective", ["x$i$j" for i in 0:1, j in 0:1], [:y0, :y1], [:z])
("projective", ["x00" "x01"; "x10" "x11"], ["y0", "y1"], ["z"])
julia> f("fun inputs", 'a':'g', Symbol.('x':'z', [0 1]))
("fun inputs", ["a", "b", "c", "d", "e", "f", "g"], ["x0" "x1"; "y0" "y1"; "z0" "z1"])
julia> @f("hello", "x#" => (1:1, 1:2), "y#" => (1:2), [:z])
"hello"
julia> (x11, x12, y1, y2, z)
("x11", "x12", "y1", "y2", "z")
julia> g(a, s::Vector{Symbol}; kv...) = (a, kv...), String.(s)
g (generic function with 1 method)
julia> AbstractAlgebra.@varnames_interface g(a, s)
@g (macro with 1 method)
julia> @g("parameters", [:x, :y], a=1, b=2; c=3)
("parameters", :c => 3, :a => 1, :b => 2)
```

`AbstractAlgebra.variable_names`

— Function```
variable_names(a...) -> Vector{Symbol}
variable_names(a::Tuple) -> Vector{Symbol}
```

Create a vector of variable names from a variable name specification.

Each argument can be either an Array of `VarName`

s, or of the form `s::VarName => iter`

, or of the form `s::VarName => (iter...)`

. Here `iter`

is supposed to be any iterable, typically a range like `1:5`

. The `:s => iter`

specification is shorthand for `["s[$i]" for i in iter]`

. Similarly `:s => (iter1, iter2)`

is shorthand for `["s[$i,$j]" for i in iter1, j in iter2]`

, and likewise for three and more iterables.

As an alternative `"s#" => iter`

is shorthand for `["s$i" for i in iter]`

. This also works for multiple iterators in that`"s#" => (iter1, iter2)`

is shorthand for `["s$i$j" for i in iter1, j in iter2]`

.

**Examples**

```
julia> AbstractAlgebra.variable_names([:x, :y])
2-element Vector{Symbol}:
:x
:y
julia> AbstractAlgebra.variable_names(:x => (0:0, 0:1), :y => 0:1, [:z])
5-element Vector{Symbol}:
Symbol("x[0, 0]")
Symbol("x[0, 1]")
Symbol("y[0]")
Symbol("y[1]")
:z
julia> AbstractAlgebra.variable_names("x#" => (0:0, 0:1), "y#" => 0:1)
4-element Vector{Symbol}:
:x00
:x01
:y0
:y1
julia> AbstractAlgebra.variable_names("x#" => 9:11)
3-element Vector{Symbol}:
:x9
:x10
:x11
julia> AbstractAlgebra.variable_names(["x$i$i" for i in 1:3])
3-element Vector{Symbol}:
:x11
:x22
:x33
julia> AbstractAlgebra.variable_names('a':'c', ['z'])
4-element Vector{Symbol}:
:a
:b
:c
:z
```

`AbstractAlgebra.reshape_to_varnames`

— Function```
reshape_to_varnames(vec::Vector{T}, varnames...) :: Tuple{Array{<:Any, T}}
reshape_to_varnames(vec::Vector{T}, varnames::Tuple) :: Tuple{Array{<:Any, T}}
```

Turn `vec`

into the shape of `varnames`

. Reverse flattening from `variable_names`

.

**Examples**

```
julia> s = ([:a, :b], "x#" => (1:1, 1:2), "y#" => 1:2, [:z]);
julia> AbstractAlgebra.reshape_to_varnames(AbstractAlgebra.variable_names(s...), s...)
([:a, :b], [:x11 :x12], [:y1, :y2], [:z])
julia> R, v = polynomial_ring(ZZ, AbstractAlgebra.variable_names(s...))
(Multivariate polynomial ring in 7 variables over integers, AbstractAlgebra.Generic.MPoly{BigInt}[a, b, x11, x12, y1, y2, z])
julia> (a, b), x, y, z = AbstractAlgebra.reshape_to_varnames(v, s...)
(AbstractAlgebra.Generic.MPoly{BigInt}[a, b], AbstractAlgebra.Generic.MPoly{BigInt}[x11 x12], AbstractAlgebra.Generic.MPoly{BigInt}[y1, y2], AbstractAlgebra.Generic.MPoly{BigInt}[z])
julia> R, (a, b), x, y, z = polynomial_ring(ZZ, s...)
(Multivariate polynomial ring in 7 variables over integers, AbstractAlgebra.Generic.MPoly{BigInt}[a, b], AbstractAlgebra.Generic.MPoly{BigInt}[x11 x12], AbstractAlgebra.Generic.MPoly{BigInt}[y1, y2], AbstractAlgebra.Generic.MPoly{BigInt}[z])
```