Python API#
This page is the reference of the Python module acres_core, the interface of ACRES Core for Python programs.
The module has one class, Batch, that steps N Polaris environments on the ACRE tile with NumPy arrays.
Build ACRES Core builds the module. Run each example in the repository root with
PYTHONPATH=Core/Build python <file>. The source is Core/Python/acres_core_module.cpp.
Index#
| Name | Kind | Description |
|---|---|---|
PHYSICS_DT |
constant | The length of one physics step. |
COMMAND_COLUMNS, STATE_COLUMNS, WHEEL_COLUMNS, ENERGY_COLUMNS |
constants | The column names of the arrays. |
SURFACE_CLASSES, GROUND_CLASSES |
constants | The names of the surface classes and of the ground classes. |
Batch |
class | Loads the world and makes the environments. |
num_envs |
property | The number of environments. |
reset |
method | Puts environments at start poses. |
step |
method | Advances all environments by one control step with drive-by-wire command rows. |
step_curvature_speed |
method | Advances all environments with a curvature command and a speed command. |
state |
property | The state array. |
wheels |
property | The wheel array. |
energy, energy_step |
properties | The energy ledger, in total and for the last control step. |
zone_crushed, zone_crushed_step |
properties | The crushed crop area for each zone. |
set_conditions |
method | Sets the soil-water scenario of environments. |
set_parameters |
method | Changes Polaris parameters of environments. |
start_log, stop_log |
methods | Records one environment into an episode log. |
lidar |
method | Makes LiDAR scans from the current poses. |
set_zones |
method | Replaces the zone raster of the crop accounts. |
height_at, surface_class_at, field_at |
methods | Queries of the terrain, the surface map and the field raster. |
nearest_obstacle |
method | The distance to the nearest obstacle. |
places |
method | The named places of the map. |
num_crop_patches, crop_patches, crushed_patches |
property, methods | The crop patches and the crushed samples. |
| Command columns | table | The 18 columns of a command row. |
| State columns | table | The 49 columns of the state array. |
| Wheel columns | table | The 13 columns of the wheel array. |
| Energy columns | table | The 24 columns of the energy arrays. |
| Zone columns | table | The 8 columns of the zone arrays. |
| LiDAR arrays | table | The beam order and the material codes. |
| Threads | section | The rules for threads. |
| Determinism | section | The results that repeat bit for bit. |
Frames and Units#
All positions are in the frame of the tile: x east, y north, z up, in metres from the centre of the tile.
The pose of a vehicle is the pose of base_footprint. This point is the middle of the rear axle on the ground.
The yaw angle is in radians, counter-clockwise from east. A compass heading \(h\) in degrees gives the yaw \(90° - h\).
Velocities and accelerations of the state array are in the body frame: x forward, y left, z up.
The drive-by-wire columns use the units of the ds_dbw_msgs messages: degrees, percent and bar.
Module Attributes#
PHYSICS_DT#
The length of one physics step in seconds. The value is 1/120.
One control step of a batch is substeps physics steps. The default control step is 12 steps, that is 0.1 s.
Column Name Lists#
Four lists of strings give the column names of the arrays. The index of a name in its list is the column index.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
COMMAND_COLUMNS |
list of 18 str | The columns of a command row. See Command columns. | ||
STATE_COLUMNS |
list of 49 str | The columns of state. See State columns. |
||
WHEEL_COLUMNS |
list of 13 str | The columns of wheels. See Wheel columns. |
||
ENERGY_COLUMNS |
list of 24 str | The columns of energy and energy_step. See Energy columns. |
Make a dictionary from a list to address a column by its name.
Class Name Lists#
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
SURFACE_CLASSES |
list of 7 str | The classes of the surface map. The index is the value of surface_under and of the wheel column surface_class. |
||
GROUND_CLASSES |
list of 6 str | The ground classes of the scouting map. The index is the column of zone_crushed on the ACRE tile. |
Batch Class#
Batch#
Batch(root, num_envs=1, threads=0, ...) loads the world one time and makes the environments.
Each environment is one Polaris with its own farm marks and its own soil-water scenario.
All environments share the terrain, the surface map, the soil data, the crop patches and the obstacle scene.
After the constructor each vehicle stands at the origin. Call reset before the first step.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
root |
str | The repository root. The module reads Acres/Content/Simulation and Core/Data below it. |
||
num_envs |
int | 1 |
The number of environments. The minimum is 1. | |
threads |
int | 0 |
The number of threads of the pool. 0 uses all hardware threads. |
|
flat |
bool | False |
True makes a level plane with one surface. It replaces the ACRE tile. |
|
flat_surface |
str | "asphalt" |
The surface of the plane: a surface of tractor.json, or bench_asphalt, bench_grass, bench_gravel. |
|
crop_season |
str | "2026" |
The crop plan ACRE/crops_<season>.json. "legacy" uses the land cover of fields.json. |
|
initial_growth |
float | 1.0 |
The growth of each planted field from 0 to 1. The value 1 is a mature crop. | |
farm |
bool | True |
False keeps no crop marks and no ruts. The step is then faster. |
|
scene |
bool | True |
Loads the obstacle scene for the LiDAR and the collisions. A plane has no scene. | |
polaris_overrides |
dict of str to float | {} |
Parameters of polaris.json by their keys, for example {"mass.occupants": 2}. |
|
occupants |
int | -1 |
The number of persons, 0 to 6. -1 keeps the value of polaris.json. |
|
substeps |
int | 12 |
The number of physics steps in one control step. | |
resend_steps |
int | 6 |
The batch sends a held command again after this number of physics steps. 0 sends it one time only. |
|
content_dir |
str | "" |
A different configuration folder. The default is <root>/Acres/Content/Simulation. |
|
farm_water |
bool | True |
The wheels read the soil water of the 4 m farm grid. False uses the water of the surface templates in tractor.json. |
The surfaces of tractor.json are asphalt, concrete, gravel, dry_soil, wet_soil, mud, sod_lane, sod and dirt_track.
A load error raises RuntimeError with the name of the file.
num_envs#
A read-only property. It gives the number of environments of the batch. The first dimension of each array has this length.
reset#
reset(indices, poses, settled=True, gear=5, drive_mode=2) puts environments at start poses.
The reset clears the farm marks, the held commands, the energy ledger and the time of these environments.
The drive-by-wire is off after a reset. The first command row must set enable to 1.
The function then writes the arrays again. The wheel contacts are valid after the first step.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
indices |
array of int, or None |
The environments. None selects all environments. |
||
poses |
array (K, 4) | m, m, rad, m/s | One row for each index: x, y, yaw and the forward speed. | |
settled |
bool | True |
True puts the vehicle on the terrain at its static ride height. False drops it from 0.2 m, as the reset of the game does. |
|
gear |
int | 5 |
The gear at the start: 1 P, 2 R, 3 N, 4 H, 5 L. | |
drive_mode |
int | 2 |
The position of the AWD switch: 0 Turf, 1 2WD, 2 AWD. |
An index out of range raises IndexError. A poses array with a different shape raises ValueError.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".", num_envs=4)
S = {name: i for i, name in enumerate(ac.STATE_COLUMNS)}
home = next(p for p in b.places()
if p[0] == "spawn-icsc-garage")
yaw = np.radians(90.0 - home[4]) # compass heading to yaw
b.reset(None, np.tile([home[2], home[3], yaw, 0.0], (4, 1)))
print(b.state[0, [S["x"], S["y"], S["z"], S["yaw"]]].round(3))
b.reset([2, 3], np.array([[210.3, -249.9, 0.0, 2.0],
[210.3, -244.0, 0.0, 2.0]]),
settled=False, gear=4)
columns = [S["speed"], S["gear"], S["time_s"]]
print(b.state[2, columns].round(3))
Output
step#
step(commands=None) advances all environments by one control step and returns the state array.
A control step is substeps physics steps of 1/120 s.
A command row has the semantics of the Dataspeed drive-by-wire messages. Each row has four subsystems: steering, throttle, brake and ULC. Each subsystem has a mode column.
- A mode that is a number is a message for this subsystem. The message arrives at the first physics step.
- The batch sends the message again each
resend_stepsphysics steps while the caller holds it. - A mode that is NaN is no message. The subsystem then gets no more messages and the watchdog disengages it after 0.1 s.
- A mode of 0 is a message that releases the subsystem at once.
- NaN in the columns
enable,gearanddrive_modekeeps the last value.
step() without an argument holds the commands of the last call.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
commands |
array (num_envs, 18), or None |
None |
One command row for each environment. See Command columns. | |
| returns | array (num_envs, 49) |
A copy of the state array after the step. |
An array with a different shape raises ValueError.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".", num_envs=2, flat=True)
b.reset(None, np.zeros((2, 4)))
C = {name: i for i, name in enumerate(ac.COMMAND_COLUMNS)}
S = {name: i for i, name in enumerate(ac.STATE_COLUMNS)}
rows = np.full((2, len(C)), np.nan) # NaN mode: no message
rows[:, C["enable"]] = 1
rows[:, C["steer_mode"]] = 2 # steering wheel, deg
rows[:, C["steer_value"]] = [0.0, 90.0]
rows[:, C["ulc_mode"]] = 1 # speed, m/s
rows[:, C["ulc_value"]] = 3.0
for _ in range(100): # 10 s
state = b.step(rows)
print(state[:, S["speed_measured"]].round(2),
state[:, S["steering_wheel_deg"]].round(1))
state = b.step() # hold for 0.1 s
print(state[:, S["time_s"]].round(3),
state[:, S["ulc_enabled"]])
Output
Example: The Watchdog
import numpy as np
import acres_core as ac
b = ac.Batch(".", flat=True)
b.reset(None, np.zeros((1, 4)))
C = {name: i for i, name in enumerate(ac.COMMAND_COLUMNS)}
S = {name: i for i, name in enumerate(ac.STATE_COLUMNS)}
on = np.full((1, len(C)), np.nan)
on[0, C["enable"]] = 1
on[0, [C["ulc_mode"], C["ulc_value"]]] = [1, 2.0]
silent = np.full((1, len(C)), np.nan)
print(b.step(on)[0, S["ulc_enabled"]]) # a message
print(b.step()[0, S["ulc_enabled"]]) # held
print(b.step(silent)[0, S["ulc_enabled"]]) # no message
Output
step_curvature_speed#
step_curvature_speed(curvature, speed) advances all environments by one control step.
It sends a steering command in curvature mode and a ULC command in speed mode to each environment.
The function sets enable to 1 and sends no throttle, brake or gear command. It returns the state array.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
curvature |
array (num_envs) |
1/m | The path curvature. A positive value turns to the left. The drive-by-wire limits it to ±0.2. | |
speed |
array (num_envs) |
m/s | The speed command of the ULC. | |
| returns | array (num_envs, 49) |
A copy of the state array after the step. |
The ULC controls the measured wheel speed, which is 0.93 times the wheel speed of the model. The path curvature is smaller than the command. The steering centre has an offset of 12.5° and the AWD mode locks the rear axle. Polaris Ranger Dynamics and Drive-by-Wire and ULC give the models.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".", num_envs=3, flat=True)
b.reset(None, np.zeros((3, 4)))
S = {name: i for i, name in enumerate(ac.STATE_COLUMNS)}
curvature = np.array([0.0, 0.05, -0.1]) # 1/m
speed = np.array([2.0, 3.0, 4.0]) # m/s
for _ in range(150): # 15 s
state = b.step_curvature_speed(curvature, speed)
print(state[:, S["speed_measured"]].round(2))
# path curvature = yaw rate / speed, 1/m
print((state[:, S["wz"]] / state[:, S["vx"]]).round(3))
Output
state#
A read-only property. It gives a copy of the state array with the shape (num_envs, 49) and the type float64.
reset, step and step_curvature_speed write the array.
State columns gives the columns.
Each access makes a new copy. Read the property one time after a step and keep the result.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".", num_envs=2)
home = next(p for p in b.places()
if p[0] == "spawn-icsc-garage")
pose = [home[2], home[3], np.pi / 2, 0.0]
b.reset(None, np.tile(pose, (2, 1)))
for _ in range(50):
b.step_curvature_speed(np.zeros(2), np.full(2, 3.0))
S = {name: i for i, name in enumerate(ac.STATE_COLUMNS)}
state = b.state
print(state.shape, state.dtype)
for name in ("time_s", "x", "y", "yaw", "speed", "rpm",
"gear", "grounded", "surface_under"):
print(f"{name:14s} {state[0, S[name]]:10.3f}")
Output
wheels#
A read-only property. It gives a copy of the wheel array with the shape (num_envs, 4, 13).
The second index is the wheel: 0 front left, 1 front right, 2 rear left, 3 rear right.
Wheel columns gives the columns.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".")
home = next(p for p in b.places()
if p[0] == "spawn-icsc-garage")
b.reset(None, np.array([[home[2], home[3], np.pi / 2, 0.0]]))
for _ in range(50):
b.step_curvature_speed(np.zeros(1), np.full(1, 3.0))
W = {name: i for i, name in enumerate(ac.WHEEL_COLUMNS)}
wheels = b.wheels
print(wheels.shape)
print(wheels[0, :, W["normal_n"]].round(0))
print(wheels[0, :, W["slip"]].round(4))
print(wheels[0, :, W["surface_class"]])
Output
energy, energy_step#
Two read-only properties with the shape (num_envs, 24). The unit is the joule.
energy is the energy ledger from the last reset. energy_step is the change during the last control step.
Energy columns gives the columns.
The ledger closes: the fuel energy is equal to the sum of the sinks and the stores.
The column ground_loss is the sum of slip, lateral and soil.
Energy and Fuel gives the model.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".", flat=True)
b.reset(None, np.zeros((1, 4)))
for _ in range(200): # 20 s at 3 m/s
b.step_curvature_speed(np.full(1, 0.05), np.full(1, 3.0))
E = {name: i for i, name in enumerate(ac.ENERGY_COLUMNS)}
total, last = b.energy[0], b.energy_step[0]
print(f"fuel {total[E['fuel']] / 1e3:.1f} kJ, "
f"ground loss {total[E['ground_loss']] / 1e3:.2f} kJ")
print(f"last control step: fuel {last[E['fuel']]:.1f} J, "
f"aero {last[E['aero']]:.2f} J")
sinks = ("engine_loss", "parasitic", "pto", "hydraulic",
"accessory_loss", "engine_kinetic", "clutch",
"driveline", "wheel_kinetic", "brake", "hysteresis",
"slip", "soil", "traction")
residual = total[E["fuel"]] - sum(total[E[k]] for k in sinks)
print(f"powertrain residual {residual:.3f} J")
Output
zone_crushed, zone_crushed_step#
Two read-only properties with the shape (num_envs, 8). The unit is the square metre.
zone_crushed is the crushed crop area in each zone from the last reset.
zone_crushed_step is the area of the last control step.
Zone columns gives the zones. The sum of the columns is the state column crop_crushed_m2.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".", num_envs=2)
# field 24 (corn), heading east
b.reset(None, np.tile([195.0, 190.0, 0.0, 0.0], (2, 1)))
speed = np.full(2, 2.0)
for _ in range(100):
state = b.step_curvature_speed(np.zeros(2), speed)
S = {name: i for i, name in enumerate(ac.STATE_COLUMNS)}
Z = {name: i for i, name in enumerate(ac.GROUND_CLASSES)}
print(b.zone_crushed.shape)
print(b.zone_crushed[0, :6].round(2),
state[0, S["crop_crushed_m2"]].round(2))
print(b.zone_crushed_step[0, Z["crop"]].round(3))
Output
set_conditions#
set_conditions(indices=None, global_fraction=nan, field_fractions=None, surface_film=0.0) sets the soil-water scenario of environments.
The scenario is constant during an episode. ACRES Core does not simulate rain or drying.
A call replaces the full scenario of the environments. The next step uses the new values.
A fraction \(f\) sets the water content \(\theta\) of the surface layer of each soil cell. The soil unit of the cell gives the wilting point \(\theta_{wp}\), the field capacity \(\theta_{fc}\) and the saturation \(\theta_{s}\).
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
indices |
array of int, or None |
None |
The environments. None selects all environments. |
|
global_fraction |
float | nan |
The fraction \(f\) of all soil cells, 0 to 1.3. NaN uses the value of farm.json, 0.5. |
|
field_fractions |
array (K, 60), or None |
None |
A fraction for each field. Column \(n\) is field \(n\). Column 0 has no function. NaN uses global_fraction. |
|
surface_film |
float | 0.0 |
The rain film on the soil surface, 0 to 1. |
Soil Water gives the model of the game.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".", num_envs=2)
b.set_conditions([0], global_fraction=0.0) # wilting point
b.set_conditions([1], global_fraction=1.3) # wet
# field 24 (corn), heading east
b.reset(None, np.tile([195.0, 190.0, 0.0, 0.0], (2, 1)))
for _ in range(100):
b.step_curvature_speed(np.zeros(2), np.full(2, 2.0))
E = {name: i for i, name in enumerate(ac.ENERGY_COLUMNS)}
W = {name: i for i, name in enumerate(ac.WHEEL_COLUMNS)}
# ground loss, kJ: dry, wet
print((b.energy[:, E["ground_loss"]] / 1e3).round(2))
# mean sinkage, mm: dry, wet
sinkage = b.wheels[:, :, W["sinkage_m"]].mean(axis=1)
print((sinkage * 1e3).round(1))
fractions = np.full((1, 60), np.nan)
fractions[0, 39] = 1.0 # field 39
b.set_conditions([0], global_fraction=0.5,
field_fractions=fractions, surface_film=0.2)
Output
set_parameters#
set_parameters(indices, overrides) gives environments a different set of Polaris parameters.
Use it for hardware shifts and for domain randomisation.
The new set is the parameter set of the batch with these overrides. Overrides of an earlier call do not stay.
CAUTION
The function initialises the vehicles of these environments again. Call reset for them before the next step.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
indices |
array of int, or None |
The environments. None selects all environments. |
||
overrides |
dict of str to float | Parameters by their keys in polaris.json. An array element has the key name[i]. |
A key that the model does not know raises KeyError.
Configuration Files gives the keys of polaris.json.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".", num_envs=2, flat=True)
b.set_parameters([1], {"mass.occupants": 4,
"steering.center_deg": 15.5})
b.reset(None, np.zeros((2, 4)))
W = {name: i for i, name in enumerate(ac.WHEEL_COLUMNS)}
b.step()
# total wheel load, N: 1 occupant and 4 occupants
print(b.wheels[:, :, W["normal_n"]].sum(axis=1).round(0))
try:
b.set_parameters([0], {"no.such.key": 1.0})
except KeyError as error:
print("KeyError:", error)
Output
start_log, stop_log#
start_log(index, path, metadata={}, agent="polaris") starts the episode log of one environment.
stop_log(index) closes the file.
The file has the format of the episode log of the game. The game can show it with an episode replay. The log gets one state message and one farm message for each physics step. It gets the episode, agent, condition and shift messages at the start and after each reset. It also contains the drive-by-wire commands at the physics steps that applied them.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
index |
int | The environment. | ||
path |
str | The path of the MCAP file. | ||
metadata |
dict of str to str | {} |
More keys for the metadata record acres_episode, for example a seed. |
|
agent |
str | "polaris" |
The name of the agent in the log. The topics of the commands start with this name. |
A file that the module cannot create raises RuntimeError.
Episode Log gives the format. Replay shows the log in the game.
Example
import numpy as np
import acres_core as ac
from mcap.reader import make_reader
b = ac.Batch(".", num_envs=2)
home = next(p for p in b.places()
if p[0] == "spawn-icsc-garage")
pose = [home[2], home[3], np.pi / 2, 0.0]
b.reset(None, np.tile(pose, (2, 1)))
b.start_log(0, "/tmp/core-episode.mcap", {"seed": "7"})
for _ in range(100): # 10 s
b.step_curvature_speed(np.full(2, 0.02), np.full(2, 3.0))
b.stop_log(0)
with open("/tmp/core-episode.mcap", "rb") as stream:
summary = make_reader(stream).get_summary()
counts = summary.statistics.channel_message_counts
for channel_id, count in sorted(counts.items()):
print(f"{count:5d} {summary.channels[channel_id].topic}")
Output
1 /sim/episode
1 /sim/agents
1200 /sim/agent_states
0 /sim/farm_events
1200 /sim/farm_stamps
1 /sim/conditions
1 /sim/shifts
0 /sim/task
200 /polaris/vehicle/steering/cmd
0 /polaris/vehicle/throttle/cmd
0 /polaris/vehicle/brake/cmd
200 /polaris/vehicle/ulc/cmd
0 /polaris/vehicle/gear/cmd
1 /polaris/vehicle/dbw_enabled
lidar#
lidar(indices=None, pattern="planar", noise=False, seed=0) makes one LiDAR scan for each selected environment.
The rays start at the pose of the vehicle after the last step. They hit the terrain and the obstacle scene.
The function returns the tuple (ranges, material).
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
indices |
array of int, or None |
None |
The environments. None selects all environments. |
|
pattern |
str | "planar" |
"planar": 360 level beams with a range of 30 m. "helios": the RoboSense Helios of the Polaris, 1800 × 32 beams. |
|
noise |
bool | False |
Adds range noise and signal noise. | |
seed |
int | 0 |
The seed of the noise and of the returns from the body of the vehicle. | |
returns ranges |
array (K, beams), float32 |
m | The range of each beam. NaN shows that the beam has no return. | |
returns material |
array (K, beams), uint8 |
The code of the object that each beam hit. |
A pattern with a different name raises ValueError.
LiDAR arrays gives the beam order and the codes. LiDAR gives the sensor model.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".", num_envs=2)
home = next(p for p in b.places()
if p[0] == "spawn-icsc-garage")
x, y = home[2], home[3]
b.reset(None, np.array([[x, y, np.pi / 2, 0.0],
[x + 6.0, y, np.pi / 2, 0.0]]))
ranges, material = b.lidar() # planar
print(ranges.shape, ranges.dtype, material.dtype)
print(f"returns {np.isfinite(ranges[0]).sum()} of 360, "
f"nearest {np.nanmin(ranges[0]):.2f} m")
ranges, material = b.lidar([0], "helios", noise=True, seed=7)
share = np.isfinite(ranges).mean()
print(ranges.shape, f"returns {share:.3f}")
codes, counts = np.unique(material, return_counts=True)
print(dict(zip(codes.tolist(), counts.tolist())))
cloud = ranges.reshape(1800, 32) # [column, ring]
Output
set_zones#
set_zones(zones, x0, y0, cell_m) replaces the zone raster of the world.
The crop accounts then put each crushed sample into the zone of its cell.
All environments share the raster. Call the function between two steps.
The cell zones[j, i] covers x from x0 + i * cell_m and y from y0 + j * cell_m. The rows go north.
A value larger than 7 counts as 7. A point outside the raster is in zone 0.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
zones |
array (rows, columns), uint8 |
The zone of each cell, 0 to 7. | ||
x0 |
float | m | The west edge of the raster. | |
y0 |
float | m | The south edge of the raster. | |
cell_m |
float | m | The size of one cell. |
An array that does not have two dimensions raises ValueError.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".")
zones = np.zeros((2, 2), np.uint8) # rows north
zones[:, 1] = 3 # east half: zone 3
b.set_zones(zones, -800.0, -800.0, 800.0)
b.reset(None, np.array([[195.0, 190.0, 0.0, 0.0]]))
for _ in range(100):
b.step_curvature_speed(np.zeros(1), np.full(1, 2.0))
print(b.zone_crushed[0].round(2))
Output
height_at, surface_class_at, field_at#
Three queries of the shared world at a point. The arguments are positional.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
height_at(x, y) |
float | m | The height of the terrain. A plane gives its height, 0. | |
surface_class_at(x, y) |
int | The class of the surface map, an index into SURFACE_CLASSES. |
||
field_at(x, y) |
int | The field number of the 4 m field raster, 1 to 59. 0 shows that the point is in no field. | ||
x, y |
float | m | The point in the frame of the tile. |
nearest_obstacle#
nearest_obstacle(x, y, max_m=30.0) gives the horizontal distance from a point to the nearest obstacle.
The obstacles are the outlines of the buildings, the bins, the props and the tree trunks.
The function returns max_m when no obstacle is nearer, or when the batch has no obstacle scene.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
x, y |
float | m | The point in the frame of the tile. | |
max_m |
float | m | 30.0 |
The search radius. |
| returns | float | m | The distance, max_m at most. |
places#
places() returns the named places of ACRE/places.json as a list of tuples.
Each tuple is (id, name, x, y, heading_deg). The heading is a compass heading in degrees.
The heading is NaN for a place that has no heading. A plane has no places.
Example
Output
num_crop_patches, crop_patches, crushed_patches#
The crop of the world is a set of patches of 1.52 m × 1 m. Each patch has 4 × 4 samples of 0.095 m².
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
num_crop_patches |
int | A read-only property: the number of patches of the world. | ||
crop_patches() |
dict | The patches of the world. See the keys below. | ||
key xyzh |
array (N, 4), float32 |
m | The origin of each patch (x, y, z) and the height of the mature plants. | |
key field |
array (N), uint8 |
The field number of each patch. | ||
key kind |
array (N), uint8 |
The crop: 0 corn, 1 soybean, 2 potato. | ||
crushed_patches(index) |
tuple of 2 arrays | The crushed patches of one environment: the patch indices (uint32) and the sample masks (uint16). |
Each bit of a mask is one crushed sample of the patch. An index out of range raises IndexError.
Crops and Ground Classes gives the crop model.
Example
import numpy as np
import acres_core as ac
b = ac.Batch(".")
print(b.num_crop_patches)
patches = b.crop_patches()
print(patches["xyzh"].shape, patches["field"].dtype,
np.bincount(patches["kind"]))
b.reset(None, np.array([[195.0, 190.0, 0.0, 0.0]]))
for _ in range(100):
b.step_curvature_speed(np.zeros(1), np.full(1, 2.0))
index, mask = b.crushed_patches(0)
samples = sum(bin(m).count("1") for m in mask.tolist())
# patches, samples, area in m2
print(len(index), samples, round(samples * 1.52 / 16, 2))
Output
Array Columns#
Command Columns#
One command row has 18 columns of the type float64. The row has the fields of the ds_dbw_msgs commands of the Polaris.
| Index | Name | Unit | Description |
|---|---|---|---|
| 0 | enable |
The system enable: 0 or 1. NaN keeps the last value. | |
| 1 | steer_mode |
The type of the steering command: 0 none, 2 angle, 3 curvature, 4 yaw rate, 14 percent. NaN: no message. | |
| 2 | steer_value |
deg, 1/m, rad/s or % | The steering command in the unit of its mode. The angle is the steering wheel angle. |
| 3 | steer_rate |
deg/s | The rate limit of the steering wheel. 0 selects the default of polaris.json, 100. |
| 4 | steer_accel |
deg/s² | The acceleration limit of the steering wheel. 0 selects the default, 500. |
| 5 | throttle_mode |
The type of the throttle command: 0 none, 13 raw percent, 14 percent. NaN: no message. | |
| 6 | throttle_value |
% | The throttle command. |
| 7 | brake_mode |
The type of the brake command: 0 none, 1 pressure, 14 percent. NaN: no message. | |
| 8 | brake_value |
bar or % | The brake command in the unit of its mode. |
| 9 | gear |
The gear command: 1 P, 2 R, 3 N, 4 H, 5 L. 0 or NaN: no new gear command. | |
| 10 | ulc_mode |
The type of the ULC command: 0 none, 1 speed, 2 acceleration. NaN: no message. | |
| 11 | ulc_value |
m/s or m/s² | The ULC command in the unit of its mode. |
| 12 | ulc_limit_accel |
m/s² | The acceleration limit of the ULC. 0 selects the default. |
| 13 | ulc_limit_decel |
m/s² | The deceleration limit of the ULC. 0 selects the default. |
| 14 | ulc_jerk_throttle |
m/s³ | The jerk limit while the acceleration increases. 0 selects the default. |
| 15 | ulc_jerk_brake |
m/s³ | The jerk limit while the acceleration decreases. 0 selects the default. |
| 16 | ulc_coast |
1 makes the ULC decelerate without the brakes. | |
| 17 | drive_mode |
The AWD switch: 0 Turf, 1 2WD, 2 AWD. NaN keeps the last value. |
A value column that is NaN counts as 0 when its mode is a number. A throttle command or a brake command overrides the ULC, as on the vehicle. Drive-by-Wire and ULC gives the model of each command.
State Columns#
The state array has 49 columns of the type float64. Flags have the values 0 and 1.
| Index | Name | Unit | Description |
|---|---|---|---|
| 0 | time_s |
s | The time from the last reset. |
| 1 to 3 | x, y, z |
m | The position of base_footprint. |
| 4 to 7 | qw, qx, qy, qz |
The orientation of the body as a quaternion. | |
| 8 | yaw |
rad | The heading of the forward axis, counter-clockwise from east. |
| 9 | pitch |
rad | The pitch angle. A positive value has the nose up. |
| 10 | roll |
rad | The roll angle. A positive value has the left side up. |
| 11 to 13 | vx, vy, vz |
m/s | The velocity of the centre of mass in the body frame. |
| 14 to 16 | wx, wy, wz |
rad/s | The angular velocity in the body frame. wz is the yaw rate. |
| 17 to 19 | ax, ay, az |
m/s² | The acceleration of the centre of mass in the body frame during the last physics step. It does not contain gravity. |
| 20 | speed |
m/s | The forward speed. It is equal to vx. |
| 21 | steering_wheel_deg |
deg | The steering wheel angle. |
| 22 | steer_ref_deg |
deg | The reference angle of the steering controller. |
| 23 | road_wheel_rad |
rad | The steering angle at the front axle, as in a bicycle model. |
| 24 | throttle_pct |
% | The throttle pedal output. |
| 25 | brake_bar |
bar | The brake line pressure. |
| 26 | gear |
The gear: 1 P, 2 R, 3 N, 4 H, 5 L. | |
| 27 | ulc_vel_ref |
m/s | The speed reference of the ULC. |
| 28 | ulc_accel_ref |
m/s² | The acceleration reference of the ULC. |
| 29 | speed_measured |
m/s | The speed that the drive-by-wire reports from the wheel speeds. It is negative in reverse. |
| 30 | rpm |
1/min | The engine speed. |
| 31 | cvt_ratio |
The ratio of the CVT belt. | |
| 32 | fuel_rate_lph |
L/h | The fuel rate. |
| 33 | fuel_used_l |
L | The fuel used from the last reset. |
| 34 | dbw_enabled |
flag | The system enable that the last command row set. |
| 35 to 38 | steer_enabled, throttle_enabled, brake_enabled, ulc_enabled |
flag | The subsystem is under drive-by-wire control. |
| 39 | grounded |
The number of wheels on the ground, 0 to 4. | |
| 40 | rear_slip |
The mean slip ratio of the rear wheels that are on the ground. | |
| 41 | crop_crushed_m2 |
m² | The crushed crop area from the last reset. |
| 42 | crop_crushed_step_m2 |
m² | The crushed crop area of the last control step. |
| 43 | crushed_wheels_step_m2 |
m² | The part of the last control step that the tyres crushed. |
| 44 | crushed_body_step_m2 |
m² | The part of the last control step that the body crushed. |
| 45 | off_map |
flag | base_footprint is less than 10 m from the edge of the tile, or outside. Always 0 on a plane. |
| 46 | collision |
flag | The outline of the vehicle touches an obstacle. |
| 47 | field_under |
The field number below base_footprint, 0 for no field. |
|
| 48 | surface_under |
The surface class below base_footprint, an index into SURFACE_CLASSES. -1 on a plane. |
The collision test uses a box of 1.6 m width from 0.5 m behind the rear axle to 0.55 m ahead of the front axle. ACRES Core finds a collision but does not stop the vehicle. A task must end the episode.
Wheel Columns#
The wheel array has 13 columns for each wheel.
| Index | Name | Unit | Description |
|---|---|---|---|
| 0 | omega |
rad/s | The spin rate of the wheel. |
| 1 | slip |
The longitudinal slip ratio. | |
| 2 | normal_n |
N | The normal load. |
| 3 | fx_n |
N | The longitudinal force on the chassis. |
| 4 | fy_n |
N | The lateral force. A positive value points to the right of the wheel. |
| 5 | sinkage_m |
m | The sinkage of the tyre, or the depth of the rut. |
| 6 | contact |
flag | The wheel touches the ground with a load of 1 N or more. |
| 7 | surface_class |
The surface class below the wheel, an index into SURFACE_CLASSES. -1 for a wheel in the air and on a plane. |
|
| 8 | field |
The field number below the wheel, 0 for no field. | |
| 9 | steer_rad |
rad | The steering angle of the wheel. A positive value is to the left. |
| 10 | drive_torque_nm |
N m | The torque of the driveline on the wheel shaft. |
| 11 | suspension_m |
m | The extension of the suspension from the mount to the wheel centre. |
| 12 | pressure_pa |
Pa | The mean contact pressure on the ground. |
Energy Columns#
The energy arrays have 24 columns. The unit of each column is the joule.
| Index | Name | Description |
|---|---|---|
| 0 | fuel |
The chemical energy of the burnt fuel. |
| 1 | engine_loss |
The fuel energy that the engine does not deliver as work. |
| 2 | parasitic |
The drag of the fan, the alternator and the pumps. |
| 3 | pto |
The work on the PTO shaft. The Polaris has none. |
| 4 | hydraulic |
The work of the hydraulic consumers. The Polaris has none. |
| 5 | accessory_loss |
The losses of the PTO driveline and the hydraulic pump. |
| 6 | engine_kinetic |
The change of the rotational energy of the engine. |
| 7 | clutch |
The slip heat of the clutch. |
| 8 | driveline |
The losses of the belt, the gearbox and the axles. |
| 9 | wheel_kinetic |
The change of the rotational energy of the wheels. |
| 10 | brake |
The heat of the brakes. |
| 11 | hysteresis |
The rolling resistance of the tyres. |
| 12 | slip |
The slip loss of the tyres. |
| 13 | soil |
The work that makes ruts: compaction and bulldozing. |
| 14 | traction |
The net longitudinal work of the tyres on the chassis. |
| 15 | axle |
The work of the driveline on the wheel shafts. It is not part of the balance. |
| 16 | drawbar |
The work that pulls an implement. The Polaris has none. |
| 17 | aero |
The air drag. |
| 18 | water |
The drag of standing water. |
| 19 | lateral |
The side-slip loss of the tyres. |
| 20 | suspension |
The work that the springs, the dampers and the soil absorb. |
| 21 | chassis_kinetic |
The change of the kinetic energy of the chassis. |
| 22 | potential |
The change of the potential energy of the chassis. |
| 23 | ground_loss |
The sum of slip, lateral and soil. |
The ledger has two balances. The fuel is equal to the sum of columns 1 to 14. The traction is equal to the sum of columns 16 to 22.
Zone Columns#
The zone arrays have 8 columns, one for each zone of the zone raster. On the ACRE tile the default raster is the ground classes of the scouting map, with cells of 0.5 m.
| Index | Name in GROUND_CLASSES |
Description |
|---|---|---|
| 0 | crop |
Crop that is not in the edge band. Also all points outside the raster. |
| 1 | lane |
A lane. |
| 2 | verge |
A verge. |
| 3 | edge_band |
The band along the edge of a field. |
| 4 | obstacle |
An obstacle. |
| 5 | other |
All other ground. |
| 6, 7 | Not used by the default raster. set_zones can use them. |
A plane has no raster. All crushed crop is then in zone 0. Scouting Map gives the ground classes.
LiDAR Arrays#
| Pattern | Beams | Beam Order | Range |
|---|---|---|---|
planar |
360 | Beam \(k\) points \(k\) degrees counter-clockwise from forward. The beams are level in the vehicle frame. | 30 m at most. Each hit gives a return. |
helios |
57600 | The index is column * 32 + ring. Column \(c\) is at \(-360\,c/1800\) degrees, clockwise. |
0.2 m to 161.3 m. The signal model decides if a hit gives a return. |
The two patterns have their origin at the mount of the Helios: 2.5 m ahead of base_footprint and 2.039 m above it.
A range of the helios pattern starts at the centre of the lens.
Code in material |
Meaning |
|---|---|
| 0 to 15 | A material of the obstacle scene: asphalt, concrete, gravel, soil, grass, mud, corn_leaf, soybean_leaf, potato_leaf, tree_foliage, shrub, bark, building, metal_bin, vehicle, other. |
| 252 | The body of the vehicle itself. Only the helios pattern has these returns. |
| 253 | A different vehicle. The batch does not make this code. |
| 254 | The terrain. |
| 255 | No return. |
Threads#
One batch has a pool of threads. reset, step, step_curvature_speed and lidar divide their work between these threads.
One thread steps one environment. The functions release the Python lock while they work.
- Call the functions of one batch from one Python thread at a time.
- The properties make copies. A different thread can read a copy while the batch steps.
- Two batches are independent. Each batch loads its own copy of the world, which takes approximately 0.5 s.
set_zoneschanges data that all environments read. Do not call it during a step.
Determinism#
A batch gives the same result bit for bit when it gets the same commands from the same start. The result does not change with the number of threads or with the number of environments. The module has no random generator. The LiDAR noise is a hash of the seed, the environment index, the step number and the beam. Time Stepping and Determinism gives the measurements and the comparison with the game.
Check with Two Thread Counts#
The example runs 8 environments for 30 s with one thread and with all threads. The three arrays are equal in all bits.
Example
import numpy as np
import acres_core as ac
def run(threads):
b = ac.Batch(".", num_envs=8, threads=threads)
home = next(p for p in b.places()
if p[0] == "spawn-icsc-garage")
x, y = home[2], home[3]
poses = np.array([[x + 6.0 * i, y, np.pi / 2, 0.0]
for i in range(8)])
b.reset(None, poses)
for k in range(300): # 30 s
b.step_curvature_speed(np.linspace(-0.08, 0.08, 8),
np.full(8, 2.0 + 0.01 * k))
return b.state, b.wheels, b.energy
one, many = run(1), run(0)
print([bool(np.array_equal(a, b)) for a, b in zip(one, many)])
Output