Skip to content

Latest commit

 

History

History
292 lines (220 loc) · 10.6 KB

README.md

File metadata and controls

292 lines (220 loc) · 10.6 KB

Procedural Generation Companion

A companion tool for the AnimalAI.

Specify task templates and procedurally generate all possible variations.

Install

pip install git+https://github.com/wschella/procgen-companion.git

or if you already installed it, maybe safer to like this:

pip install --upgrade --force-reinstall git+https://github.com/wschella/procgen-companion.git

Citation

If you use this software, please cite it as below.

@software{Schellaert_ProcGen_2023,
  author = {Schellaert, Wout},
  title = {{AnimalAI Procedural Generation Companion}},
  url = {https://github.com/wschella/procgen-companion},
  version = {0.1.0},
  month = {5},
  year = {2023}
}

Usage

$ procgen --help
usage: procgen-companion [-h] {sample,gen,sample-bulk,gen-bulk,count-bulk} ...

Procedurally generate variations of an AnimalAI based on a task template.

positional arguments:
  {sample,gen,sample-bulk,gen-bulk,count-bulk}
    sample              Sample variations of a task definition.
    gen                 Generate variations of a task definition.
    sample-bulk         Sample variations of multiple task definitions.
    gen-bulk            Generate variations of multiple task definitions.
    count-bulk          Count the number of variations of multiple task definitions.

A procgen template is structured like an AnimalAI .yaml file, but one can make use of certain special tags like !ProcList, !ProcColor, !ProcIf that define the allowed variations of a field's value.

Run procgen gen template.yaml...

... and the procgen-companion tool will generate all possible combinations of these fields, and output a .yaml file for each variation in path/to/template_variations/, which are directly usable by the AnimalAI environment.

The example below is not a valid AnimalAI config, but that doesn't matter right now.

# example.template.yaml
field1: !ProcList [0, 120]
field2: !ProcList [90, 180]

If we run procgen example.template.yaml, four new files will be created:

# example_variations/example_1.yaml
field1: 0
field2: 90

# example_variations/example_2.yaml
field1: 0
field2: 180

# example_variations/example_3.yaml
field1: 120
field2: 90

# example_variations/example_4.yaml
field1: 120
field2: 180

The template used will also be copied example_variations/template.yaml.

Every valid AnimalAI .yaml file is also a valid procgen-companion template. All the fields you don't want to procedurally generate will be ignored and copied.

It is also possible to take a random sample instead of generating all possible combinations, or to generate in bulk. See procgen --help or ``procgen [subcommand] --help` for more info.

Tags

All tags used in procgen-companion start with !Proc.

Select variations from a list of options

With !ProcList, you define all the possible options a value can take. The items of the list can be any kind of valid yaml, i.e. lists, tags, mappings (see below). But !ProcList does not support nested !Proc-tags, i.e. the items of the list can not be !Proc-tags.

# Define three possible options for a scalar field value.
!ProcList [0, 90, 180]
# Define three possible options for a mapping
!ProcList [
  !Vector3 { x: 10, y: 0, z: 1 },
  !Vector3 { x: 20, y: 1, z: 5 },
  !Vector3 { x: 20, y: 1, z: 10 },
]

# Exactly the same as above, just with different notation.
!ProcList
- !Vector3 { x: 10, y: 0, z: 1 }
- !Vector3 { x: 20, y: 1, z: 5 }
- !Vector3 { x: 20, y: 1, z: 10 }

You can also perfectly do this instead:

!Vector3 { x: !ProcList [2, 4, 6], y: 1, z: !ProcList [5, 10, 15] }

This defines 3x3=9 possible variations: !Vector3 {x: 2, y: 1, z: 5}, !Vector3 {x: 2, y: 1, z: 10}, !Vector3 {x: 2, y: 1, z: 15}, and the same for x: 4 and x: 6.

Generate random colors

This will generate a new color each time, until 10 different ones have been picked. The colors will be picked from a fixed list of colors. If the amount specified here is larger than the list of colors we use, we will throw an error so you can fix it or ask us to create more colors.

!ProcColor 5

Varying all dimensions of a Vec3 at the same time

A common use case is varying all dimensions of a Vec3 at the same time, e.g. scaling the vector with a single scalar number.

The base !Vector3 will not be included in the possible options. If you want that, just include 1 as a scale factor.

!ProcVector3Scaled
base: !Vector3 { x: 2, y: 1, z: 1 }
scales: [1, 2, 3, 4]

The base is an optional argument. If it is not present, the unit vector will be assumed, i.e. !Vector3 { x: 1, y: 1, z: 1}. The following is thus also valid:

!ProcVector3Scaled
scales: [1, 2, 3, 4]

Make the same choice for a list of values

You can use !ProcRepeatChoice when for example three walls need to vary their color, but it must always be the same color, i.e. they must vary at the same time. This tag will produce a list of values, each of them identical. The value field can contain any valid yaml including (nested) !Proc-tags, as that is what is designed for. Any choice made in the !Proc tags will be copied. It is allowed for the value field to not contain any !Proc tags. Everything works just the same.

colors: !ProcRepeatChoice
  amount: 3 # The amount of repetitions, i.e. the number of elements in the resulting list.
  value: !ProcColor 5 # The amount of different values a list item can take.

# One possible realization of the possible 5 would like like this:
colors:
  - !RGB {r: 128, g: 128, b: 128 }
  - !RGB {r: 128, g: 128, b: 128 }
  - !RGB {r: 128, g: 128, b: 128 }

Restricting variations

You can use !ProcRestrictCombinations when you know some part of the yaml file is responsible for many different possibilities, but you want to limit the total, without restricting any of the individual choices. An example is when seven walls need to vary their color, you don't want it to be the same color, and you only want 25 different combinations in total. This works by uniformly random sampling all the !Proc-tags nested inside.

The item field does not need to be a list as is the case right now. It can be anything.

!ProcRestrictCombinations
amount: 25 # Number of possible values this tag will generate.
item:
  - !ProcColor 10
  - !ProcColor 10
  - !ProcColor 10
  - !ProcColor 10
  - !ProcColor 10
  - !ProcColor 10
  - !ProcColor 10
  - !ProcColor 10

Condition the selection of variations on the values of other fields

!ProcIf
value: [agent.positions.0.x, agents.positions.0.z]
cases: [[1, 5], [1, 7], [3, !R [10, 20]]]
then: [90, 120, 150]
default: 0

Depending on the the values the variables take, different values are generated by the !ProcIf tag. A !ProcIf tag is reactive. It does not increase the number of combinations, only sets the values of some fields in an existing combination.

Conceptually, if the variables in the value field are equal to the numbers defined in the cases field (or within a range !R) then the corresponding value in the then field will be selected. If none of the cases match the value of the default field is taken. If your cases overlap by accident, the first matching case will be taken.

Here (first case): if x=1 and z=5, then we take 90. Instead of a single number, you can also specify a range with !R. Here (last case): if x=3 and z between 10 and 20 (inclusive), then we take 150.

  • The value field can be a string, or a list of strings. Multiple strings indicate there multiple variables we care about. Each string is a reference to a specific field in the generated yaml file (see below).
  • The cases field is always list. If value is a list, cases must be a list of lists, and an inner list needs to match the length of value. The outer list can be any length, each element represent a "case". It can be of length 1 (representing a single "if", with default acting as the "else".)
  • The then field results is always a list. Its length equals the length of the outer list of the cases field.
  • The default is always a scalar, i.e. a number, a !Vector3, etc. It is an optional argument. If it is not provided, the tool will throw an error and stop execution when any variables take on a value that is not in the list of cases.

TODO: On references. Generated yaml file. First part of the dot separated must refer to an AnimalAI mapping (!ArenaConfig, !Arena, !Item, !Vector3, ...) with the corresponding id field. TODO: !ProcIf's can refer to other values decided by !ProcIf's (not circular of course). Add example for this.

# With only 1 variable
!ProcIf
value: agent.positions.0.z,
cases: [5, 7, 10]
then: [90, 120, 150]
default: 0

# With only 1 case and 1 variable
!ProcIf
value: agent.positions.0.z,
cases: [5]
then: [180]
default: 0

# With only 1 case, but two variables (one of them in a range)
!ProcIf
value: [agent.positions.0.x, agents.positions.0.z]
cases: [[1, !R [5, 10]]]
then: [180]
default: 0

# With three cases, and no default.
# The tool will throw an error and stop if agent.positions.0.x takes values other than those specified, e.g. 15.
!ProcIf
value: agent.positions.0.x
cases: [1, !R [2, 9], 10]
then: [0, 90, 180]

Labelling filenames

If you want to change the filename depending on some of the generated values, you can use can add a labels field to !ProcIf and!ProcVector3Scaled, or you can use the new !ProcListLabelled.

Make sure that your labels are filename friendly (e.g. no colons).

!ProcIf
value: [agent.positions.0.x, agents.positions.0.z]
cases: [[1, 5], [1, 7], [3, !R [10, 20]]]
then: [90, 120, 150]
labels: [dist_close, dist_med, dist_far]
default: 0
default_label: dist_default
# Currently no support for a default label yet.

!ProcVector3Scaled
base: !Vector3 { x: 2, y: 1, z: 1 }
scales: [1, 2, 3]
labels: [size_small, size_med, size_large]

!ProcListLabelled
- label: dist_far
  value: !Vector3 { x: 20, y: 0, z: 1 }
- label: dist_medium
  value: !Vector3 { x: 20, y: 1, z: 5 }
- label: dist_close
  value: !Vector3 { x: 20, y: 1, z: 10 }

There is also a new special section proc_meta in the beginning of the yaml file, which will not be present in the generate AnimalAI config, but allows you to specify independent labels. You have to use !ProcIfLabels, which works exactly like !ProcIf, but it will have the corresponding then value included in the filename.

!ArenaConfig
proc_meta:
  proc_labels:
    - !ProcIfLabels
      value: [agent.positions.0.x, agents.positions.0.z]
      cases: [[1, 5], [1, 7], [3, !R [10, 20]]]
      labels: [dist_far, dist_close, dist_medium]
      default: dist_default # Optional
arenas:
  0: !Arena
    items: # ...