9 Commits

Author SHA1 Message Date
7dfe5140c6 fix: update version number in pyproject.toml 2026-01-30 20:39:12 +01:00
563afb2fe9 Merge pull request #3 from pim-n/dev
Release 0.2.0 adding segmented road feature to cli
2026-01-30 20:17:06 +01:00
602d0d08d0 Merge pull request #2 from pim-n/feature-segmented-road-gen-cli
Add segmented road capabilities to the CLI entrypoint.
2026-01-30 20:07:12 +01:00
f5313532b1 update README to add segmented road example 2026-01-30 20:05:36 +01:00
17719a8865 increase default alpha to help stability under default conditions 2026-01-30 19:59:34 +01:00
371d0d1e75 Improve documentation 2026-01-30 19:56:58 +01:00
39625e4166 fix generic reference to np.random to internal class rng 2026-01-30 19:51:48 +01:00
ddb962569c write test for SegmentedRoadGenerator reproducability 2026-01-30 19:50:48 +01:00
21be94c94f Add CLI options for a segmented road 2026-01-30 19:45:03 +01:00
5 changed files with 85 additions and 48 deletions

View File

@ -54,10 +54,20 @@ road-gen random -l 1000 -s 10 -v 10 --save
which will produce 3 files starting with the unique seed number used to generate the road.
## Example - segmented road
A minimal segmented road needs, besides length $L$, step size $\Delta s$ and velocity $v$, a list of segments.
```
road-gen segments --segments straight turn_left straight turn_right --length 1000 --ds 10 --velocity 10
```
The parameter $\alpha$ represents the concentration factor for the [Dirichlet distribution](https://numpy.org/doc/2.0/reference/random/generated/numpy.random.dirichlet.html).
## Reproducability
You can reproduce results by adding a seed with the `--seed` flag.
## Other
For more info, just see `road-gen --help` or `road-gen random --help`.
For more info, see `road-gen --help` or `road-gen random --help`.

View File

@ -1,6 +1,6 @@
[project]
name = "road_gen"
version = "0.1.0"
version = "0.2.0"
authors = [
{ name="Pim Nelissen", email="pi0274ne-s@student.lu.se" },
]

View File

@ -33,27 +33,25 @@ class SegmentedRoadGenerator(BaseRoadGenerator):
def generate(
self,
segments: list[str],
alpha: float = 1.0
alpha: float = 100
) -> Tuple[np.ndarray, np.ndarray]:
"""Generate a curvature profile from a list of segments.
Args:
segments (list[str]): List of segments.
length (int): Total length of the path in meters.
velocity (float): Velocity of the car in km/h.
ds (float, optional): Step size. Defaults to 1.0.
alpha (float, optional): Dirichlet concentration parameter. A higher value leads to more uniform apportionment of the length amongst the segments, while a lower value allows more random apportionment. Defaults to 1.0.
Raises:
ValueError: _description_
ValueError: _description_
ValueError: "No valid radius for this turn segment" means a turn is too tight given its segment length and the velocity. To fix this, you can try to reduce the amount of segments or increase length. Increasing alpha (Dirichlet concentration parameter) can also help because this reduces the odds of very small lengths being assigned to turn segments.
Returns:
np.ndarray: _description_
Tuple[np.ndarray, np.ndarray]: x and y coordinates of the waypoints describing the random road.
"""
if not all(segment in prefabs.PREFABS.keys() for segment in segments):
raise ValueError(f"Invalid segment type provided. Available choices: {prefabs.SEGMENTS.keys()}")
self.segments = segments
self.alpha = alpha
num_points = int(self.length / self.ds)
# divide num_points into len(segments) randomly sized parts.
@ -84,7 +82,7 @@ class SegmentedRoadGenerator(BaseRoadGenerator):
if R_min > R_max_angle:
raise ValueError("No valid radius for this turn segment")
rand_radius = np.random.uniform(R_min, R_max_angle)
rand_radius = self._rng.uniform(R_min, R_max_angle)
if seg_name.startswith("u_turn"):
curvature_s = seg_function(rand_radius)

View File

@ -4,9 +4,8 @@ from matplotlib import pyplot as plt
from .generators.random_road_generator import RandomRoadGenerator
from .generators.segmented_road_generator import SegmentedRoadGenerator
from .plotting.plot_road import plot_road
from .prefabs import prefabs
from .utils import export
def add_common_args(parser):
@ -30,9 +29,13 @@ def main():
random_parser.add_argument("--straight_section_max_rel_size", type=float, required=False, help="The maximum size that straight section(s) can have relative to the total length of the path. Defaults to 0.1.")
add_common_args(random_parser)
segment_parser = subparsers.add_parser("segments", help="Generate a road according to a list of segments.")
segment_parser.add_argument("--segments", nargs="+", type=str, required=True, help=f"List of segments. Choose from {str(prefabs.PREFABS.keys())}")
segment_parser.add_argument("--alpha", type=float, required=False, help="Dirichlet distribution concentration parameter. A high alpha distributes total length more evenly across segments.")
add_common_args(segment_parser)
args = parser.parse_args()
if args.method == "random":
try:
if not all(v > 0 for v in (args.length, args.ds, args.velocity)):
raise ValueError("Length, step size, and velocity must be positive values.")
@ -50,15 +53,23 @@ def main():
if args.seed:
init_args["seed"] = args.seed
generator = RandomRoadGenerator(**init_args)
generate_args = {}
if args.method == "random":
generator = RandomRoadGenerator(**init_args)
if args.straight_section_prob:
generate_args["straight_section_prob"] = float(args.straight_section_prob)
if args.straight_section_max_rel_size:
generate_args["straight_section_max_rel_size"] = float(args.straight_section_max_rel_size)
elif args.method == "segments":
generator = SegmentedRoadGenerator(**init_args)
if args.alpha:
generate_args["alpha"] = args.alpha
generate_args["segments"] = list(args.segments)
x, y = generator.generate(**generate_args)
if args.save:

View File

@ -3,24 +3,42 @@ import numpy as np
import pytest
from road_gen.generators.random_road_generator import RandomRoadGenerator
from road_gen.generators.segmented_road_generator import SegmentedRoadGenerator
@pytest.fixture
def test_params():
def base_params():
length = 1_000
ds = 10
velocity = 10
return length, ds, velocity
@pytest.fixture
def seg_params():
segments = ["straight", "turn_left", "straight", "turn_right"]
alpha = 100
def test_random_road_generator(test_params):
return segments, alpha
def test_random_road_generator(base_params):
"""Test whether fixing the seed for RandomRoadGenerator produces identical output."""
generator_1 = RandomRoadGenerator(*test_params)
generator_1 = RandomRoadGenerator(*base_params)
x1, y1 = generator_1.generate()
generator_2 = RandomRoadGenerator(seed = generator_1.seed, *test_params)
generator_2 = RandomRoadGenerator(seed = generator_1.seed, *base_params)
x2, y2 = generator_2.generate()
assert np.array_equal(x1, x2)
assert np.array_equal(y1, y2)
def test_segmented_road_generator(base_params, seg_params):
"""Test whether fixing the seed for SegmentedRoadGenerator produces identical output."""
generator_1 = SegmentedRoadGenerator(*base_params)
x1, y1 = generator_1.generate(*seg_params)
generator_2 = SegmentedRoadGenerator(seed = generator_1.seed, *base_params)
x2, y2 = generator_2.generate(*seg_params)
assert np.array_equal(x1, x2)
assert np.array_equal(y1, y2)