Notes for users of other computer algebra systems

OSCAR is written in Julia, so an OSCAR session is just a Julia session. Many of the differences described here are therefore differences in behaviour and syntax between Julia and the CAS you are used to. This page collects differences that affect users of all such systems. Notes for users of specific systems follow on separate pages:

Help wanted

These pages are far from complete. If you are missing something here, or if a difference between OSCAR and the system you know took you a while to understand, please tell us about it, for example by opening an issue on GitHub or on Slack. Contributions to these pages are very welcome.

Integers and rational numbers

An integer literal such as 2 is a 64 bit machine integer in Julia (only literals too large for 64 bits become big integers automatically). Arithmetic with machine integers silently wraps around on overflow, and some functions detect the overflow and throw an error instead.

julia> 2^100
0

julia> factorial(21)
ERROR: OverflowError: 21 is too large to look up in the table; consider using `factorial(big(21))` instead
[...]

Use OSCAR's integers (elements of ZZ) whenever the numbers may get large.

julia> ZZ(2)^100
1267650600228229401496703205376

julia> factorial(ZZ(21))
51090942171709440000

The error message above suggests big(21), a Julia BigInt, which works as well; OSCAR's own functions return elements of ZZ, so only the literals you type yourself are affected.

The quotient of two integers is a floating point number in Julia, and // creates rational numbers. OSCAR makes a subtle but important distinction between / and //.

julia> 3/4
0.75

julia> QQ(3, 4)
3//4

Every object has a parent

Each element of an algebraic structure knows its parent, that is, the structure it belongs to: the ring of a polynomial, the group of a permutation, the field of a number. parent(x) returns it. Operations that involve several elements usually require that the parents coincide. Some conversions happen automatically, for example integers and rational numbers can be combined with elements of most rings: for any ring $R$ there is exactly one ring homomorphism from the integers into $R$, and it extends uniquely to a partial map from the rational numbers, defined wherever the denominator is invertible in $R$. This is what R(5) and R(7//2) compute, and such an automatic conversion is called a coercion. In other cases, an element has to be moved explicitly into the required structure, by calling the parent as if it were a function.

julia> ZZ(7) + QQ(3, 2)
17//2

julia> F = GF(7);

julia> x = F(3)
3

julia> parent(x)
Prime field of characteristic 7

julia> x + 5
1

Many constructors return more than one object

Functions that construct a substructure or a quotient usually also return the map that connects it with the original structure, and functions that construct rings with generators also return the generators. In such cases, the result is a tuple, and the individual parts of it can be assigned to variables in one go.

julia> R, (x, y) = polynomial_ring(QQ, [:x, :y]);

julia> G = symmetric_group(4);

julia> H, emb = sub(G, [cperm(G, [1, 2, 3])]);

julia> H
Permutation group of degree 4

julia> emb
Group homomorphism
  from permutation group of degree 4
  to symmetric group of degree 4

Ignore parts that you do not need by assigning them to _, or pick a part by indexing.

julia> G = symmetric_group(4);

julia> Z, _ = center(G);

julia> A = derived_subgroup(G)[1]
Alternating group of degree 4

The same applies for example to quo, kernel, image, number_field, and residue_ring.

Semicolons and output

In an interactive session, Julia prints the value of each entered expression, and a trailing semicolon suppresses this output.

Inside a Julia function, nothing is printed unless you call println or use the @show macro.

Indexing starts at 1

Like in GAP, Magma, and Singular, and unlike in Python (and hence in SageMath) and polymake, the first entry of a list has index 1. Analogously, matrix row and column indices start from 1. Ranges include both endpoints: 1:5 consists of the numbers 1 to 5.

Names of functions

OSCAR functions have snake_case names, see the section on Naming conventions. A predicate has a name that starts with is_, for example is_prime, and the name of a function that modifies one of its arguments ends with an exclamation mark, for example push!.

Functions are not attached to objects, that is, one writes order(G) rather than G.order(). One name serves all kinds of objects it makes sense for: there is a single order, for groups as well as for group elements, rather than an order_of_group and an order_of_element; likewise degree for polynomials, field extensions, and permutation groups, and length for lists and other collections. Which implementation runs is decided by the types of the arguments.

Optional arguments are usually given by name, after a semicolon, for example kernel(M; side = :right).

Some hints for finding the function you are looking for:

  • Guessing the snake_case version of the name that you know from the system you are used to often works.
  • Type the beginning of a name and hit the tab key to see all completions.
  • ?name shows the documentation of the function name, and methods(name) shows for which types of arguments it is defined.
  • methodswith(typeof(x); supertypes = true) lists all functions that have a method for objects of the same type as x.
  • apropos("text") lists all functions whose documentation mentions the given text.
  • The search field at the top of this manual searches the whole manual.

Interactive sessions

  • When an error occurs, or when you hit ctrl-C, Julia returns to the prompt. There is no break loop, in contrast to GAP and Magma.
  • Julia compiles a function the first time it is called with arguments of a given type, so the first call of a command can take noticeably longer than later ones. A slow first impression says nothing about the speed of the computation itself.
  • Measure the runtime of a computation with the @time macro: @time f(x).
  • Read a file with Julia code with include("file.jl").
  • Save OSCAR objects to a file with save("file.mrdi", x) and read them back with load("file.mrdi"), see Serialization.