Skip to content

Carry the dtype of a Discrete space through a round trip - #346

Open
DenisDrobyshev wants to merge 2 commits into
Farama-Foundation:mainfrom
DenisDrobyshev:discrete-dtype-round-trip
Open

DenisDrobyshev wants to merge 2 commits into
Farama-Foundation:mainfrom
DenisDrobyshev:discrete-dtype-round-trip

Conversation

@DenisDrobyshev

Copy link
Copy Markdown

Description

_serialize_discrete writes "int64" for every Discrete space and
_deserialize_discrete ignores the field, so a space built with another dtype
comes back as a different space:

space = spaces.Discrete(5, dtype=np.int32)
reconstructed = deserialize_space(serialize_space(space))

reconstructed.dtype     # int64
reconstructed == space  # False

The comment on the line — # this seems to be hardcoded in Gymnasium — was
true when it was written. Discrete gained a dtype argument in Gymnasium
1.3.0, and DiscreteArray-style specs that default to int32 are common enough
that a dataset can carry one.

test_space_serialize_deserialize compares the two serialized strings rather
than the two spaces, so it stays green either way. That is why this went
unnoticed.

Changes

  • Serialising writes str(space.dtype). No version guard is needed there:
    Discrete.dtype exists on every supported Gymnasium and is int64 on the
    older ones, which is exactly what was written before, so old and new files
    agree.

  • Deserialising passes the dtype from Gymnasium 1.3.0 onwards. pyproject.toml
    declares gymnasium>=0.28.1, so on an older install a dataset that asks for
    a narrower dtype is reported:

    ValueError: The dataset declares a Discrete space of dtype int32, which needs
    Gymnasium 1.3.0 or later; 1.2.0 is installed.
    

    Silently widening it would hand back a space the dataset never described.
    Every file written before this change says int64, which matches the default,
    so nothing existing hits that path.

The version check is the same shape as the one in
Farama-Foundation/Shimmy#157: major and minor compared as integers rather than
as strings, so 1.10 stays above 1.3, and no dependency on packaging, which
is not in this project's requirements.

Tests

test_discrete_keeps_its_dtype, parametrised over int32 and int64. The int32
case fails on main and passes here; int64 passes either way, which is where
the two defaults coincide. The case is skipped with a reason below Gymnasium
1.3.0. tests/test_serialization.py is 21 passed, pre-commit run clean on
both files.

#345 touches the same two files, so whichever of the two lands second will need
a rebase — say the word and I will push it.

DenisDrobyshev and others added 2 commits August 20, 2026 17:36
The serializer wrote "int64" for every Discrete space and the deserializer
ignored the field, so a space built with another dtype came back as a
different space:

    space = spaces.Discrete(5, dtype=np.int32)
    deserialize_space(serialize_space(space)) == space   # False, dtype int64

Discrete only gained a dtype argument in Gymnasium 1.3.0 and this package
supports 0.28.1 upwards, so the argument is passed from 1.3.0 onwards and a
dataset that needs a narrower dtype on an older Gymnasium is reported rather
than silently widened. Serialising needs no such guard: Discrete.dtype exists
on every supported version and is int64 there, which is what was written
before.

test_space_serialize_deserialize compares the two serialized strings rather
than the spaces, which is why this went unnoticed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants