Geo-AID logo

Introduction

Geo-AID is a tool to generate figures based off of rules given in the script file. Its main purpose is to minimize the pain related to drawing figures for certain mathematical problems or theorems. It's currently in the early development.

Note: Geo-AID is not designed to produce perfect output. It is designed to produce best output. This means that it might sacrifice partial accuracy in favor of better readability. It may also make other compromises and produce unexpected results at times. If you're having trouble with this kind of behavior, visit Dealing with complicated figures

As an entry point, Geo-AID uses GeoScript - a language used to describe a figure. Aside from that, special parameters can be set as command line arguments. This book is meant to serve as a guide to anyone starting to use Geo-AID and as a reference to anyone who wants to know more.

Note: This book is held up-do-date with the latest released version on crates.io.

Beginner guide

This guide will get you through your first steps with Geo-AID. You will install the tool (if you haven't already) and create your first figure. Then, you will learn how to deal with figures that require distances and how to use Geo-AID parameters to your advantage.

Getting started

Installation

Before you start using Geo-AID, you'll need to install it. Unfortunately, it does not come in the form of precompiled binaries, and you'll need some other tools to build it. First, install Rust and Cargo. Once you're done, there are two ways of setting up Geo-AID:

The first way is to simply use the cargo install method:

cargo install geo-aid

This has the advantage of installing Geo-AID globally, so that you can run it from anywhere. It will take care of all dependencies for you. Building may take some time, but once it's done, it's done for good (unless you'll want to update it).

The second way is to clone/download the GitHub repository ( remember to get the last vX.X version) and build Geo-AID yourself. In this, case, you will also need the geo_aid_derive source. To download the repos, you'll need to download the .zip file and unpack it somewhere. If you want to clone it (recommended), you'll need git. The clone way is shown below

mkdir geo-aid
cd geo-aid
git clone https://github.com/DragonGamesStudios/Geo-AID.git
git clone https://github.com/DragonGamesStudios/geo_aid_derive.git
cd Geo-AID
git checkout v0.2

It's important that if you compile from source, you should preserve this file structure:

| some_folder:
    | geo-aid
    | geo_aid_derive

Then, either build it with cargo build --release and use the produced executable or run it with cargo run --release -- <geo-aid arguments here>.

Run the program with the --version flag to check if it works properly. You can also run geo-aid --help (replace geo-aid with cargo run --release -- if using the second way) if you want to see how to use the tool CLI (you can also check the CLI reference).

The rest of this book will assume you have the command globally available (as if it was installed).

Your first figure

In order to use Geo-AID, we have to tell it exactly what we want. We can do this with a script file. Geo-AID uses a special language called GeoScript that lets us give specific instructions. Create the following file with a name of your choice, say figure.geo:

let A = Point();
let B = Point();
let C = Point();

AB = AC;
AB = BC;

and run it with the following command:

geo-aid figure.geo figure.svg

After a short wait a new file figure.svg should show up. Open it in any SVG previewer (could even be your browser) and gaze at your first figure in awe:

An equilateral triangle ABC

Ok, but what exactly happened here? Let's take a closer look at the script we've just given to Geo-AID:

First, we have the three let statements. These statements are used to create variables. In our case, these variables are points created with the Point() function. You can also add special display properties to the variable definitions to change how they are rendered. For example, if you change the first line to the following:

let A [label = G] = Point();

you should get something like this:

An equilateral triangle BCG

You can find out more about the display system here.

After the variable definitions, we have the two lines:

AB = AC;
AB = BC;

These are called rule statements. They represent a relationship between the left hand side and the right hand side. In this case, the relationship is the equality of lengths AB, BC and AC. It's worth noting that the equality sign represents a rule, not a definition or a redefinition. GeoScript is a description language, not a programming one.

Geo-AID takes these requirements and attempts to create a figure that meets them.

let statements and rules can be sometimes combined by adding rules after the left hand side of a let statement. For example:

# This is a comment
let A = Point();
let B = Point();
let C = Point();
let r = dst(BC) < AB;

let omega = Circle(A, r);

Here, r is set to the distance BC and said to be smaller than AB.

Expressions

Geo-AID mostly operates on expressions. They are variable definitions and both sides of rules. Expressions can be mathematical operations, function calls, lines, distances and literals. All expressions produce values of certain types. These can be divided into the following categories:

  • primitives
  • point collections
  • bundle types

Primitives are points, lines, circles and scalars. They're what the generator operates on and what everything is ultimately compiled into. Everything else is just an abstraction over these primitives. Additionally, scalars can have units. Performing addition or subtraction on scalars with incompatible units is an error.

Point collections are sequences of point letters, like AB, ABC, GFED, X, A'V. For a name to be collectable, it has to be a single, uppercase letter with an arbitrary number of ticks (') following it, that represents a point. Point collections can also be constructed using &(...) syntax. You can use them on the left-hand side of let statements to unpack the rhs expression onto a point collection. Note, however, that not all types have that option.

let ABC = &(intersection(XY, GH), mid(G, H), intersection(TU, KL));

Bundle types are essentially like structs in programming languages. They have their primitive fields and functions defined on them that can be used for more comfortable workflow.

Implicit conversions

Geo-AID is capable of performing some implicit conversions:

  • Unknown-unit scalars (usually literals) can be converted into a scalar with a distance unit.
  • A point collection consisting of two points can be converted into a line or the distance between the two points, depending on the context.
  • A point collection of length one is always automatically converted into a point.
  • When performing multiplication/division over a scalar with a unit and a scalar with an unknown unit, the latter is automatically converted into a unitless scalar (standard scalar in mathematics).
  • Any variable defined with an unknown-unit scalar is assumed to be unit-less.

Shortening the code with iterators

Many figures feature multiple points and defining each one with a separate let statement can feel very verbose. To help that, GeoScript has a powerful iterator system. Iterators can be used in let statements and rules on both sides. A sequence of expressions separated by a comma is called an implicit iterator. Using these, we can collapse multiple lines of a script into a single one. For example, our first figure script becomes the following:

let A, B, C = Point();

AB = AC, BC;

Iterators are expanded into multiple rules/statement by simply iterating over the given sequence. Note that implicit iterators take precedence over binary arithmetic operators. Here's a few examples:

AB, BC = CD, EF;

# Becomes
AB = CD;
BC = EF;
AB < XY + YZ, OI;

# Becomes.
AB < XY + YZ;
AB < XY + OI;

whereas

AB < (XY + YZ), OI;

# Becomes

AB < XY + YZ;
AB < OI;

To use implicit iterators inside a function call, simply put parentheses around them: intersection(AB, (KL, XY))

Another type of iterator is an explicit iterator. These are written in the following way:

AB = $1(AC, BC);

The above example is equivalent to just writing

AB = AC, BC;

The number after the dollar sign is the id of the iterator. If you're using a single id, they function just like implicit iterators. However, when using multiple different ids, you can get some interesting results:

$1(AB, CD) < $2(XY, YZ);

# Becomes
AB < XY;
CD < XY;
AB < YZ;
CD < YZ;

Explicit iterators can also be nested, allowing for even more complicated rules. For example:

$1(AB, BC) > $1(x, $2(a, b)), $3(9, 4, 3);

# Becomes
AB > x;
BC > a;
BC > b;
AB > 9;
AB > 4;
AB > 3;
BC > 9;
BC > 4;
BC > 3;

There are a few important things to remember about iterators:

  • All implicit iterators have an id of 0;
  • All iterators of the same id must have the same length;
  • The left hand side of let statements only accept implicit iterators;
  • The right hand side of let statements accepts at most one level of iteration;
  • The right hand side of a let statement may only contain iterators, if so does the left side;
  • All iterators must have at least two variants;
  • An iterator with id x must not contain another iterator with id x;
  • Iterator length must not exceed 255;
  • Iterator id must be an integer;

Exact rules regarding the iterators can be found here.

Dealing with complicated figures

Sometimes you will stumble on a figure that is quite challenging to draw, even for Geo-AID. In these cases, it's worth knowing a few tricks to guide Geo-AID through them.

When Geo-AID generates a figure, it does so with a certain amount of samples, over multiple cycles, until a certain condition (currently: average quality delta over the last x cycles goes below a certain value) is met. That's a lot of different generation parameters and all of them are modifiable.

  1. Sample count (-s or --samples option, 512 by default)

By modifying this parameter, you change how many different versions of a generation cycle are created. The higher the value, the more likely Geo-AID is to find the right spot for every point. It will, however, take more time, and it might make certain flaws of Geo-AID's generation more visible.

  1. Generation break average delta threshold (-d option, 0.0001 by default)

Lowering this makes Geo-AID go on with generation for a little longer, essentially postponing the moment it decides it won't really get much better.

  1. Count of last records used in calculating the average delta (-m option, 128 by default)

Increasing this makes Geo-AID take more of the last cycles into consideration when deciding whether to stop.

  1. Generation engine (-e or --engine, glide by default)

By default, Geo-AID uses glide as its optimization engine as it should generally perform better than rage. It might, however, be worth a try to switch the engine.

  1. Maximum adjustment per generation cycle (-a option, 0.5 by default)

This modifies how much can a single point/scalar be changed when adjusting for another cycle. Keep in mind that this is only a base for calculations. In reality, the amount of adjustment allowed depends on the given point's quality and is different between different workers to allow both big and small changes in the same generation cycle. This parameter only works with the Rage engine.


Usually, the most visible effect comes from increasing the sample count.

Ultimately the best way of increasing odds for Geo-AID is to write the script as well as you can, relying strongly on defining points with expressions. The golden rule is: the fewer rules, the better.

Command-Line Interface of Geo-AID

Command Overview:

Geo-AID

Usage: Geo-AID [OPTIONS] <INPUT> <OUTPUT>

Arguments:
  • <INPUT> — The input script file
  • <OUTPUT> — The output target
Options:
  • -d, --delta-max-mean <DELTA_MAX_MEAN> — The maximum mean quality delta. Geo-AID will keep doing generation cycles until the average quality delta over the last m cycles gets below d.

    Default value: 0.0001

  • -w, --worker-count <WORKER_COUNT> — The count of threads to use for generation

    Default value: 32

  • -s, --samples - The count of samples to use for generation. Each engine interprets it differently

    Default value: 512

  • -e, --engine - The generation engine to use.

    Default value: glide

    Possible values:

    • glide: The gradient descent engine
    • rage: Random adjustment based engine
  • -m, --mean-count <MEAN_COUNT> — The count of last deltas to include in mean calculation. Geo-AID will keep doing generation cycles until the average quality delta over the last m cycles gets below d.

    Default value: 128

  • -s, --strictness <STRICTNESS> — How strict the generator treats the rules. The higher, the more strict. Can't be zero.

    Default value: 2.0

  • -a, --adjustment-max <ADJUSTMENT_MAX> — Maximal adjustment of an adjustable during generation. Treated differently for different adjustables.

    Default value: 0.5

  • -r, --renderer <RENDERER> — What renderer to use.

    Default value: svg

    Possible values:

    • latex: The LaTeX + tikz renderer
    • svg: The SVG format renderer
    • json: The JSON (machine-readable) format renderer
    • raw: The raw (human-readable) format renderer
  • --width <WIDTH> — Canvas width (treated very differently for LaTeX)

    Default value: 500

  • --height <HEIGHT> — Canvas height (treated very differently for LaTeX)

    Default value: 500

  • -l, --log <LOG> — Where to put the log output. Geo-AID has a logging feature for concise information about the rendering process (quality and time).


This document was generated with the help of clap-markdown.

Renderers

Geo-AID supports four different renderers, also called drawers. One more is planned.

LaTeX

Using LaTeX, tikz and tikz-euclide, one of the two recommended ways of drawing the figure.

SVG

Outputs the figure in the svg format. One of the two - and the most tested - ways of drawing the figure.

JSON

Machine-readable JSON format according to the Schema available in Geo-AID's repository. Can be used to integrate other tools with Geo-AID.

Raw

A human-readable format, pure text. Contains descriptions of the positions of each object in the figure.

GeoGebra

Work in progress, Geo-AID will support outputting in the GeoGebra format importable in the very tool.

GeoScript reference

Syntax

This chapter describes the syntax of GeoScript. The notation used is the same as defined in The Rust Reference. Geo-AID expects utf8 encoded files. All whitespaces are ignored beyond distinguishing separate tokens. The basis is the Figure.

Properties

Syntax
Properties :
   [ Property (; Property)* ]

Property :
   NAMED_IDENT = PropertyValue

PropertyValue :
      NUMBER
   | IDENT
   | STRING
   | RawString

RawString :
   ! STRING

Property values

bool

Boolean properties represent true or false for certain properties. A true value can be represented as the following:

1, true, enabled, on, "true", "enabled", "on"

A false value can be represented like this:

0, false, disabled, off, "false", "disabled", "off"

NOTE: Cannot be represented by a raw string. NOTE: In case of boolean values, parsing of identifiers and strings is case-insensitive.

number

Number values accept positive integers and floats. They cannot be expressed by idents or strings. Used for weights.

Style

Style properties tell Geo-AID how to display a given line or a circle. Available options are: SOLID, DASHED, BOLD, and DOTTED. They can be represented using identifiers or non-raw strings. When parsed, case is ignored.

MathString

MathString properties usually represent label contents. MathStrings are used to write normal text while also allowing lower indices and a restricted set of mathematical characters, like greek letters.

Parsing

Identifiers

If the identifier is a single character or a character code representing a letter (character codes explained below), a number of primes (also explained below), and a _ followed by digits, it can be parsed as a MathString containing only that character.

Examples:

A
B_12
C'
D''_456

Raw strings

Raw strings are parsed as a set of ASCII characters without any additional processing. Useful for injecting LaTeX into point labels, should it be necessary.

Examples:

!"\mathbb{X}^\prime"
!"Hello, World!"
!"_{}}}Everything is literal}"

Strings

Strings are parsed like raw strings with a few important exceptions:

  • Single quotes (') are parsed as primes;
  • Everything directly after a _, until, but not including, a space, is parsed as being in lower index;
  • Lower index cannot be used inside a lower index;
  • Longer text with spaces can be put inside a lower index if delimited by braces ({});
  • Text inside brackets ([]) is parsed as a character code and outputs a special character with that code;
  • \\ before a character inserts that character regardless of the above rules (it does not, however, enable using " in a string. You can use [quote] for that purpose).

Examples:

"A"
"B_12"
"C'_{Hello, World!}"
"[Alpha] [quote]label [alpha][quote]"

Character codes

Character codes are used to represent special characters. Currently, Geo-AID only supports greek letters - in form of the names of those letters, where the case of the first letter decides the case of the output letter - and quotes ("), written as qoute.

Primes

Primes, in MathStrings, are ticks often seen beside points. They are often used to represent a point after certain transformations, like symmetry or rotation (looks like A'). In MathStrings, all non-escaped (\\) single quotes (') are treated as those.

LineType

LineType describes whether a line should be displayed as a continuous line, a ray or a segment. This property is allowed in a few functions. Possible values are: LINE, RAY, SEGMENT. The default depends on the context.

Expressions

Syntax
Expression<iter> :
      ImplicitIteratoronly if iter = true
   | SimpleExpression
   | Expression<iter> BinOp Expression<iter>;

BinOp :
   + | - | * | /

SimpleExpression :
   -? SimpleExpressionKind (^ Exponent)? Properties?

SimpleExpressionKind :
      NAMES
   | NUMBER
   | ExplicitIterator
   | PointCollectionConstructor

UnOp :
   -

Exponentiation :
   SimpleExpressionKind ^ -? Exponent

Exponent :
      INTEGER
   ( INTEGER / INTEGER )

PointCollectionConstructor :
   & ( Expression<false> (, Expression<false>)* )

Expressions represent all values in GeoScript. A simple example of an expression is a variable reference with the variable's value or a number literal. After most expressions display options can be given, modifying how the expression affects the final figure visually.

Names

Names can also be used as expressions. See here for more details.

Operators

Binary operators all have the standard mathematical operation order. Unary operators always precede binary operators and implicit iterators always precede all operators. You can define your own order with parenthesis.

Currently, Geo-AID supports only addition, subtraction, multiplication and division as binary operators and negation as unary.

Weight in binary operations is applied to both of their operands. In unary operations, it is directly applied to their single operand.

Exponentiation

Exponentiation takes precedence over every other operator, including negation. It can be written as base^exp, where base is the raised expression and exp is the exponent, either a literal integer or a fraction in the form (nom / denom) with nom and denom being integers and denom being nonzero. The exponent can also be negated by including a - in front of it (in case of fraction exponents, before the parenthesis).

When raising a value to a power, its unit is also raised.

Weights, like other properties are passed on to raised expressions.

Point Collections

Expressions can also be used to construct point collections out of other expressions with &(A, B, ...) syntax. All expressions inside must be convertible to points.

Weights on point collections are treated as though they were applied to each of the collected points.

Figure

Syntax
Figure :
   Statement*

Statement :
      FlagStatement
   | LetStatement
   | RuleStatement
   | RefStatement
   | ;

A Figure describes how a figure should be generated in terms of generator flags, variable definitions and rules.

Flags

Syntax
FlagStatement :
   FlagName FlagValue

FlagName :
   @ NAMED_IDENT (. NAMED_IDENT)* :

FlagValue :
      NAMED_IDENT
   | FlagSet
   | NUMBER

FlagSet :
   { FlagStatement* }

Flags modify the behavior of Geo-AID's generator. They have default values, though some of them need to be explicitly specified to enable certain features (e.g. distance_literals).

A flag statement composes of the flag's name and its value. Each flag has a predefined type and will only accept values of that type. Identifier flags accept identifiers in general, though usually only a subset of identifiers is valid, representing certain behavior options. Boolean flags are used to enable or disable certain features/modifications to the standard behavior. They accept 1, true, enabled and yes as a true value and 0, false, disabled and no as a false value. Number flags may accept floats or integers, depending on the flag. Flag sets are special flags that categorize other flags. If you want to modify multiple flags of the same category, simply set the value of the parent set flag to a flag set with the respective statements.

Flag statements also accept a syntatic sugar for flag indexing. Instead of writing

@optimizations {
    @identical_expressions: false
}

You can simply write

@optimizations.identical_expressions: false

Identifiers

Lexer
IDENT :
      NAMED_IDENT
   | POINT_COLLECTION

NAMED_IDENT :
   Start Continue*

POINT_COLLECTION :
   (Point '*)+

Where Start is any unicode character with the Alphabetic property or an underscore (_) character, Continue is Start or a tick (') character and Point is any unicode character with the Uppercase property.

Identifiers mostly represent variables, though they may also serve as a rule operator, a function, a value for a display property or a flag value. See also: names.

Point collections are a special kind of identifiers. They essentially represent a sequence of variables, each being a point.

Iterators

Syntax
ImplicitIterator :
   Expression<false> (, Expression<false>)+

ExplicitIterator :
   $ INTEGER ( Expression<false> (, Expression<false>)* )

Iterators can be used in let statements and rules on both sides. A sequence of expressions separated by a comma is called an implicit iterator. Using these, multiple lines of a script can be collapsed into a single one. For example:

let A, B, C = Point();

AB = AC, BC;

Iterators are expanded into multiple rules/statement by simply iterating over the given sequence. Implicit iterators take precedence over any arithmetic operators. Here's a few examples:

AB, BC = CD, EF;

# Becomes
AB = CD;
BC = EF;
AB < XY + YZ, OI;

# Becomes.
AB < XY + YZ;
AB < XY + OI;

whereas

AB < (XY + YZ), OI;

# Becomes

AB < XY + YZ;
AB < OI;

To use implicit iterators inside a function call, simply put parentheses around them: intersection(AB, (KL, XY))

Another type of iterator is an explicit iterator. These are written in the following way:

AB = $1(AC, BC);

The above example is equivalent to just writing

AB = AC, BC;

The number after the dollar sign is the id of the iterator. If only a single id is used, they function just like implicit iterators. However, when using multiple different ids, more complicated results can be achieved:

$1(AB, CD) < $2(XY, YZ);

# Becomes
AB < XY;
CD < XY;
AB < YZ;
CD < YZ;

Explicit iterators can also be nested, allowing for even more complicated rules. For example:

$1(AB, BC) > $1(x, $2(a, b)), $3(9, 4, 3);

# Becomes
AB > x;
BC > a;
BC > b;
AB > 9;
AB > 4;
AB > 3;
BC > 9;
BC > 4;
BC > 3;

Iterators have a few rules. Not obeying them causes compilation errors.

  • All implicit iterators have an id of 0;
  • All iterators of the same id must have the same length;
  • The left hand side of let statements only accept implicit iterators;
  • The right hand side of let statements accepts at most one level of iteration;
  • The right hand side of a let statement may only contain iterators, if so does the left side;
  • All iterators must have at least two variants;
  • An iterator with id x must not contain another iterator with id x;
  • Iterator length must not exceed 255;
  • Iterator id must be an integer;

Names

Syntax
Name<iter> :
      IDENT
   | ExprCall
   | FieldIndex;
   | ( Expression<true> )

ExprCall :
   Name ( (Expression<false> (, Expression<false>)*)? )\

Interpreting names

Names can be interpreted in two ways: as values or as function references. When used as values, identifiers denote variable accesses, field indices represent accessing certain fields of values, parenthesised expressions represent the contained expressions and function calls are rather self-explanatory. When used as function references, idents represent global functions, field accesses represent methods and parenthesised expressions, like function calls, are not allowed.

Variables

Identifiers denote variables created with let statements. When given weight to a named identifier, it will affect the weighing of the definition after expansion (the definition itself won't be affected). When a weight is given to a point collection, it will act as though it was given to each of the referenced points. Note, however, that since point collections are simply abstractions, there is no guarantee that each point of the collection will be used. It the vast majority of cases that can however be guaranteed.

Fields

Different types have different fields. You should seek specifics in the documentations of respective types. A field index has the following form: name . field. The name is expected to be a value.

Weights on field accesses are treated like on variables.

Functions

The call syntax, name(arg1, arg2, ...) can be used to call functions with specified parameters. Functions, beyond being constructive expressions, can modify the visual output of the figure, e.g. add a line/ray. This behavior can be usually modified using display options. Some functions accept parameter groups, allowing infinite number of parameters. All functions return a single value. Implicit iterators cannot be used in function parameters, unless surrounded by parentheses.

The name of the function must be a function reference. If it's an ident, it's treated as a global function. If it's a field index, the function is treated like a method. Methods are special functions defined on types. They use that type as their first parameter, and are generally associated to that type. For specific examples, look at type documentations.

Weights given to function calls affect the parameters and the expression generated by the function.

Parentheses

Putting expressions in parentheses allows for modifying the order of operations or allowing the use of explicit iterators in contexts, where it wouldn't be normally possible. They also allow using complex expressions as names. Weights applied to parenthesised expressions are applied to their contained expressions. Note, that sometimes applied weight to parenthesised expressions is the only way to apply weight to the entire expression (for example: binary operations).

Numbers

Lexer
NUMBER :
      INTEGER
   | FLOAT

INTEGER :
   Digit+
FLOAT :
   INTEGER . Digit*

Where Digit is an ASCII digit (0-9). Either integers or decimals.

Properties

Syntax
Properties :
   [ Property (; Property)* ]

Property :
   NAMED_IDENT = PropertyValue

PropertyValue :
      NUMBER
   | IDENT
   | STRING
   | RawString

RawString :
   ! STRING

Property values

bool

Boolean properties represent true or false for certain properties. A true value can be represented as the following:

1, true, enabled, on, "true", "enabled", "on"

A false value can be represented like this:

0, false, disabled, off, "false", "disabled", "off"

NOTE: Cannot be represented by a raw string. NOTE: In case of boolean values, parsing of identifiers and strings is case-insensitive.

number

Number values accept positive integers and floats. They cannot be expressed by idents or strings. Used for weights.

Style

Style properties tell Geo-AID how to display a given line or a circle. Available options are: SOLID, DASHED, BOLD, and DOTTED. They can be represented using identifiers or non-raw strings. When parsed, case is ignored.

MathString

MathString properties usually represent label contents. MathStrings are used to write normal text while also allowing lower indices and a restricted set of mathematical characters, like greek letters.

Parsing

Identifiers

If the identifier is a single character or a character code representing a letter (character codes explained below), a number of primes (also explained below), and a _ followed by digits, it can be parsed as a MathString containing only that character.

Examples:

A
B_12
C'
D''_456

Raw strings

Raw strings are parsed as a set of ASCII characters without any additional processing. Useful for injecting LaTeX into point labels, should it be necessary.

Examples:

!"\mathbb{X}^\prime"
!"Hello, World!"
!"_{}}}Everything is literal}"

Strings

Strings are parsed like raw strings with a few important exceptions:

  • Single quotes (') are parsed as primes;
  • Everything directly after a _, until, but not including, a space, is parsed as being in lower index;
  • Lower index cannot be used inside a lower index;
  • Longer text with spaces can be put inside a lower index if delimited by braces ({});
  • Text inside brackets ([]) is parsed as a character code and outputs a special character with that code;
  • \\ before a character inserts that character regardless of the above rules (it does not, however, enable using " in a string. You can use [quote] for that purpose).

Examples:

"A"
"B_12"
"C'_{Hello, World!}"
"[Alpha] [quote]label [alpha][quote]"

Character codes

Character codes are used to represent special characters. Currently, Geo-AID only supports greek letters - in form of the names of those letters, where the case of the first letter decides the case of the output letter - and quotes ("), written as qoute.

Primes

Primes, in MathStrings, are ticks often seen beside points. They are often used to represent a point after certain transformations, like symmetry or rotation (looks like A'). In MathStrings, all non-escaped (\\) single quotes (') are treated as those.

LineType

LineType describes whether a line should be displayed as a continuous line, a ray or a segment. This property is allowed in a few functions. Possible values are: LINE, RAY, SEGMENT. The default depends on the context.

Ref statements

Syntax
RefStatement :
      Properties ? Expression<true> ;

Ref statements can be used to display expressions without any side effects.

Properties of refs

If provided a non-zero weight property, a ref statement generates a bias rule. Bias rules are rules that are always true. They can be used to artificially make certain adjustables more stable. Beyond that, any display properties defined on them are treated like properties of the ref-ed expression.

Rules

Syntax
RuleStatement :
   Properties Expression<true> RuleOp Expression<true>* ;

RuleOp :
      < | <= | = | >= | >
   | IDENT
   | ! RuleOp

Rules are the basic building blocks of a figure. They define relationships between figure objects. Geo-AID attempts to generate a figure that obeys them as good as it can. Rules tie two expressions (left and right hand side) with a relationship, otherwise known as the rule operator. Currently supported rule operators are all comparison operators. When given an identifier, a proper defined rule operator is looked up and compiled accordingly (currently none supported). Rules can also be inverted with an exclamation mark in front of the operator.

Weights on non-ident rules are assigned to them directly and end up being used directly in the generation process. Weights on ident rules are treated differently depending on the rule. You should seek documentation on them in docs for respective operators.

Variables

Syntax
LetStatement :
   let VariableDefinition (, VariableDefinition)* = Expression<true> (RuleOp Expression<true>)* ;

VariableDefinition :
   IDENT Properties?

A let statement creates variables given on the left hand side. The lhs of the statement can contain multiple variables. In that, case if the rhs has no iteration, all variables will be set to the given definition (no the same value, though). If there is one level of iteration, all variables will get their respective definition. More levels of iteration are not allowed.

The rhs expression of the statement can either become the variable's definition or it can be unpacked onto a point collection. Point collection variables are invalid. A point collection may be used on the right hand side only if the identifier on the left is a point collection.

After each variable name there can be given properties that are later applied to the defining expression(s).

The let statement accepts rules after its right hand side. They behave as if the lhs was a sequence of variable accesses in a 0-id iterator.

Types

GeoScript has two kinds of types: primitives and bundles.

Primitives - the points, lines, scalars and circles, are the building blocks of every value in GeoScript. Bundles, on the other hand, consist of multiple primitives.

The compiler is capable of performing some implicit conversions:

  • Unknown-unit scalars (usually literals) can be converted into a scalar with a distance unit.
  • A point collection consisting of two points can be converted into a line or the distance between the two points, depending on the context.
  • A point collection of length one is always automatically converted into a point.
  • When performing multiplication/division over a scalar with a unit and a scalar with an unknown unit, the latter is automatically converted into a unit-less scalar (standard scalar in mathematics).
  • Any variable defined with an unknown-unit scalar is assumed to be unit-less.

Primitives

Scalar

A scalar is a simple real value with a unit - a unit is a product of integer powers of simple units. The simple units are:

  • Distance
  • Angle

Any scalar, whose unit cannot be determined, is assumed to be unit-less. Scalars in this reference are denoted as Scalar(<unit>).

Note: A literal will never be coerced to an angle, since that would introduce uncertainty whether it should be treated as given in radians or degrees. Instead, look for their respective functions.

Point

A point is defined as a point on a Euclidean plane. Denoted as Point.

Points have two fields: x and y, denoting the respective coordinate values. Use them carefully, though, as there are no guarantees as to what values they might be.

Circle

A circle is given a center and a radius. It is a set of points with the distance to its center equal to its radius. Denoted as Circle.

Circles have two fields: center and radius, both of which are self-explanatory.

Line

A line is a line in Euclidean sense. Denoted as Line.

Bundle types

Bundle types are zero-cost abstractions over primitives. There are two types of bundles: point collections and named bundles.

Point collections

Point collections are simply ordered collections of points. It is never a separate entity, only an abstraction over a set of points. Denoted as <length>-P. If <length> is given as 0, it means a collection of any length. Most functions that accept points as arguments, also accept point collections.

Named Bundles

Named bundles are similar to structs in C. They have names and named fields of any type (accessible through field indexing). Denoted with their unique names different from the names of any other type.

Segment

Segment {
    A: [Point](primitives.md#point),
    B: [Point](primitives.md#point)
}

Segments have two fields denoting their ends.

Methods

  • len()

Return type: Scalar (distance)

Returns: the distance AB.

Displays: exactly what dst displays, except that the draw_segment property is false by default.

Operators

Standard arithmetic operations - addition (+), subtraction (-), multiplication (*) and division (/) are only allowed between scalars. Addition and subtraction must only be performed between scalars of the same unit, whereas multiplication and division can be done with any two scalars. The resulting unit will simply be a product of the operation. Beyond that, negation with the - operator can be performed on any scalar.

Functions

angle

  • angle(ABC: 3-P)
  • angle(A: Point, B: Point, C: Point)

Return type: Scalar (angle)

Returns: measurement of the angle ABC

Displays: the angle's arms.

The function accepts additional properties in the form of:

#![allow(unused)]
fn main() {
struct Angle {
    display_arms: bool, // Default: true,
    arms_type: LineType, // Default: SEGMENT
}
}

display_arms decides whether the arms should be displayed and arms_type decides whether they should be segments, rays or lines. The assumed order for rays is B -> A and B -> C;

  • angle(k: Line, l: Line)

Return type: Scalar (angle)

Returns: measurement of the angle between k and l. Which angle, depends on the order of the lines. For predictable outcome, the point versions are strongly recommended.

bisector

  • bisector(AB: 2-P)
  • bisector(A: Point, B: Point)

Return type: Line

Returns: a bisector of the segment AB - a perpendicular line passing through its center.

  • bisector(ABC: 3-P)
  • bisector(A: Point, B: Point, C: Point)

Return type: Line

Returns: a bisector of the angle ABC - a line between lines AB and BC, where each point is in the same distance from both of these lines.

Displays: the angle's arms.

The function accepts additional properties in the form of:

#![allow(unused)]
fn main() {
struct Bisector {
    display_arms: bool, // Default: true,
    arms_type: LineType, // Default: SEGMENT
}
}

display_arms decides whether the arms should be displayed and arms_type decides whether they should be segments, rays or lines. The assumed order for rays is B -> A and B -> C;

  • angle(k: Line, l: Line)

Circle

  • Circle(center: Point, radius: Scalar (distance))
  • circle(radius: Scalar (distance), center: Point)

Return type: Circle

Returns: a circle with the given center and radius.

  • Circle()

Return type: Circle

Returns: a circle with an adjusted (free point) center and an adjusted (free scalar) radius.

degrees

  • degrees(value: Scalar (no unit))

Return type: Scalar (angle)

Returns: an angle with the given measurement in degrees. Related: radians

dst

  • dst(AB: 2-P)
  • dst(A: Point, B: Point)

Return type: Scalar (distance)

Returns: the distance between points A and B.

Displays: the segment AB.

The function accepts additional properties in the form of:

#![allow(unused)]
fn main() {
struct Dst {
    display_segment: bool, // Default: true,
    style: Style, // Default: SOLID
}
}

display_segment decides whether the segment should be displayed and style decides how it should be displayed.

  • dst(P: Point, k: Line)
  • dst(k: Line, P: Point)

Return type: Scalar (distance)

Returns: the distance between point P and line k.

Displays: the segment between P and its perpendicular projection onto k.

The function accepts additional properties in the form of:

#![allow(unused)]
fn main() {
struct Dst {
    display_segment: bool, // Default: true,
    style: Style, // Default: DASHED
}
}

display_segment decides whether the segment should be displayed and style decides how it should be displayed.

  • dst(value: Scalar (no unit / distance))

Return type: Scalar (angle)

Returns: the value with a distance unit.

intersection

  • intersection(k: Line, l: Line)

Return type: Point

Returns: intersection of lines k and l.

Displays: By default doesn't display the point dot. Modifiable with properties.

Note: display_dot property is not currently supported.

mid

Note: The following functions allow any positive numbers of arguments.

  • mid(v_1: Scalar (any unit u), v_2 Scalar (the same unit u), ..., v_n: Scalar (the same unit u))

Return type: Scalar (the same unit u)

Returns: The average value of v_1, v_2, ... v_n.

  • mid(P_1: Point, P_2 Point, ..., P_n: Point)

Return type: Point

Returns: The middle point of P_1, P_2, ... P_n. Special cases: when n=2, the middle of a segment; When n=3, the centroid of a triangle.

parallel_through

  • parallel_through(P: Point, k: Line)
  • parallel_through(k: Line, P: Point)

Return type: Line

Returns: a line parallel to k, passing through P.

perpendicular_through

  • perpendicular_through(P: Point, k: Line)
  • perpendicular_through(k: Line, P: Point)

Return type: Line

Returns: a line perpendicular to k, passing through P.

Point

  • Point()

Return type: Point

Returns: an adjusted (free) point.

radians

  • radians(value: Scalar (no unit))

Return type: Scalar (angle)

Returns: an angle with the given measurement in radians. Related: degrees

Segment

  • Segment(AB: 2-P)
  • Segment(A: Point, B: Point)

Return type: Segment

Returns: the segment AB.

Displays: the segment AB.

The function accepts additional properties in the form of:

#![allow(unused)]
fn main() {
struct Segment {
    display_segment: bool, // Default: true,
    style: Style, // Default: SOLID
}
}

display_segment decides whether the segment should be displayed and style decides how it should be displayed.

Rule operators

Comparison

The operators <, <=, >, >= are only allowed between Scalars of the same unit. They are simple comparison operators with their rules evaluated based on the relative difference between the two values.

The operator = (and its negation, !=) is allowed between Scalars of the same unit and Points. Its rule is evaluated based on the absolute distance between the two values.

lies_on

All uses accept weight property.

  • P: Point lies_on k: Line

Tells Geo-AID that point P lies on (has zero distance) from line k. Note: zero distance rules do not have any impact on the distance variable and decrease figure stability much less than other distance rules.

  • P: Point lies_on k: Segment

Tells Geo-AID that point P lies on (has zero distance) from the line of segment k and between its ends. Note: zero distance rules do not have any impact on the distance variable and decrease figure stability much less than other distance rules.

  • P: Point lies_on omega: Circle

Tells Geo-AID that point P lies on (has zero distance) from circle omega. Note: zero distance rules do not have any impact on the distance variable and decrease figure stability much less than other distance rules.

  • col: 0-P lies_on omega: Circle

Tells Geo-AID that points in the collection col lie on (have zero distance) from circle omega in exactly the given order. Note: zero distance rules do not have any impact on the distance variable and decrease figure stability much less than other distance rules.

Note: When negated, creates rules for the points not to be on the circle. Points that are on the circle, just not in the given order will not satisfy this rule.

Display system

The display system decides which expressions are displayed and which are not. Its options are expressed through properties.

What is displayed?

Most expressions accept a display (bool) property, that has a default value based on the constructive-ness of the expression. Beyond that, expressions have a tree-like structure. For example, an expression representing an orthocenter of triangle ABC.

intersection(perpendicular_through(AB, C), perpendicular_through(BC, A))

It's semantic structure is the following:

- intersection
    - perpendicular_through
        - AB (line)
            - A
            - B
        - C
    - perpendicular_through
        - BC (line)
            - B
            - C
        - A

Now, the value of the display property of a node in that tree (e.g. the first perpendicular_through) decides not only whether the expression itself is displayed, but also whether its child nodes (the AB and C in our examples) are displayed.

Display properties are a simple sequence of key-value pairs used to modify how the figure should be displayed. They're accepted in expressions, rules and variable definitions. Display properties with invalid values or unexpected properties will cause an error and the ones with invalid names will be ignored.

The principle the display system works with is that an expression is displayed by default iff it's constructive.

What does it mean to display an expression?

To display an expression means to display its visual representation in its final figure. As simple example, to display a bisector(ABC) is to add a line representing the bisector of the angle ABC to the output figure.

What is a constructive expression?

A constructive expression is one that constructs a new object: a point, a line, etc. In practice, only variable references are non-constructive GeoScript - that is, referencing a variable either through a name or through a point collection won't display anything related to that variable (note that anything that could be displayed with it, should already be marked for display while processing the definition). It is however worth noting that point collection construction is constructive. Additionally, collections of length 2 are constructive, as they are converted to a different type (a line or a distance). Certain expressions, even though constructive, don't expect any properties simply because there's nothing to display. An example of that is a literal number.

Basic properties for types

All types have their basic properties assigned to them. These are the following.

Point

#![allow(unused)]
fn main() {
struct Properties {
    display: bool, // Default: true
    label: MathString, // Default: empty (except look at next section)
    display_label: bool, // Default: true
    display_dot: bool // Default: true
}
}

The display property decides whether the point should be displayed. label gives the point a label and display_label decides if it is to be displayed. If display_dot is true, a small dot is displayed in the point's position.

NOTE: display_dot has currently no effect and the dot is always displayed. NOTE: Labels currently have poor support in SVG.

Line

#![allow(unused)]
fn main() {
struct Line {
    display: bool, // Default: true,
    label: MathString, // Default empty (look at next section),
    display_label: bool, // Default: true
    style: Style, // Default: SOLID
}
}

display, label and display_label work like with points. The style property decides how the line should be displayed (what "brush" should be used).

NOTE: Labels don't currently work with lines.

Circle

#![allow(unused)]
fn main() {
struct Circle {
    display: bool, // Default: true,
    label: MathString, // Default empty (look at next section),
    display_label: bool, // Default: true
    style: Style, // Default: SOLID
}
}

display, label, display_label and style work like with lines.

NOTE: Labels don't currently work with circles.

Scalar

#![allow(unused)]
fn main() {
struct Scalar {
    display: bool, // Default: true,
    label: MathString, // Default empty (look at next section)
    display_label: Style, // Default: SOLID
}
}

All properties work like described before.

NOTE: Labels don't currently work with scalars.

PointCollection

#![allow(unused)]
fn main() {
struct PointCollection {
    display: bool // Default: true
}
}

The display property works as usual.

Point collections also have special behavior when they are used in the context of lines or distances (see: conversions). Specifically, when converted to a distance measurement or a line, they also accept properties related to Lines (see above).

Bundle

All bundle types accept a display property.


Variables and literals don't accept any properties, no matter the type. Beyond that, additional properties may be added depending on the kind of construction (used function). Details on those are in the documentation of respective functions.

Properties on variable definitions

Variable definitions display their defining expressions. Properties defined on definitions are passed onto the expression. Additionally, if no label is given, the variable name is parsed as a MathString and used as a label if the parse was successful (and if there is no display_label=false).

Properties on rules

Currently, rules only accept a display property and display both of their sides.

Weight system

The weight system of Geo-AID is one of the core mechanisms of its generator. It directly modifies how much each adjustable will be affected by certain rules.

How are weights computed?

Every entity present in a rule gets assigned a weight - by default equal to 1. Weights themselves, however, ultimately only apply to adjustables - values adjusted by the generator in the figure creation process. The weight system applies only to rules as they are the main set of instructions on how to generate a figure. Let's take a look at an example:

let A, B, C = Point();

AC = BC;

The above script is a simple description of an isosceles triangle. In order to understand weights, we must first understand adjustables.

Adjustables are values adjusted by the generator - this means free points, free scalars, points on lines, etc. An example of a function generating an adjustable is the Point() function. It creates a free point able to be adjusted in both dimensions. Here, we generate three different free points. One for each of A, B, and C.

After the definition there is a single rule: AB = BC. As you can see, there are three adjustables here: A. B, C. Despite B appears twice, all adjustables get the same weight of 1.

How are weights applied?

By the time weight computation has finished, each rule has a weight assigned to each adjustable in the figure specifying how much the rule affects the adjustable.

When it is all computed, each weight of the latter set of weights is normalized, that is: squeezed into the range [0, 1] by dividing each weight by the sum of them all. Finally, each weight is multiplied by the rule's assigned weight. This way each adjustable has a weight assigned to each rule.

When rules are evaluated, they are given a quality in range [0, 1]. This way, for each adjustable, all values can be put into pairs (quality, weight) for each rule. From that a weighed mean is calculated (sum of the products quality * weight divided by the sum of weights). The result is the final quality of an adjustable.

The final quality affects how much an adjustable is adjusted when making corrections.

In short, rule weights affect how much a given adjustable is affected by the rule's quality in comparison to other rules.

How to modify weights?

In general, weights are modified by adding a number-type weight property in square brackets:

[weight = 2]
AB = BC;

Specifics regarding how do weight properties affect certain rules are in their respective documentations.

Flags

Flags are divided into flag groups.

Ungrouped

These are flags directly in the global scope of flags. They're not in any group and generally refer to some specific settings.

point_inequalities

Type: bool

Default: true

Description: Automatically adds rules for inequalities of all point entities.

Notes: It increases the rule count by a lot, decreases figure stability. Experiments with it have rendered it unsuitable for most uses.

optimizations

This group of flags modifies how the compiler and generator optimize the figure.

Math behind Geo-AID