Skip to content

Commit

Permalink
Merge pull request #5 from rolling-stock-scheduling/feature/enhance-i…
Browse files Browse the repository at this point in the history
…t-test

Feature/enhance it test
  • Loading branch information
munterfi authored Jun 11, 2024
2 parents 956a05f + e3f3bce commit df82385
Show file tree
Hide file tree
Showing 30 changed files with 409 additions and 35,744 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ buildNumber.properties
src/main/java/ch/sbb/rssched/local/

# integration test data
integration-test/input/**/*.xml.gz
integration-test/input/**/*events.xml.gz
integration-test/output/
64 changes: 59 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ The project has the following package structure:

## Usage

### Using Command-Line Application

Read the request configuration from an Excel file and optionally send the request to the solver using the
RsschedMatsimClient:

```sh
mvn exec:java -Dexec.args="path/to/config_file.xlsx -h localhost -p 3000 -d"
```

Options:

- **Required:**
- `config_file`: Path to the Excel configuration file.
- **Optional:**
- `-h / --host`: Scheduler base URL (default: "http://localhost").
- `-p / --port`: Scheduler port (default: 3000).
- `-d / --dry-run`: If present, do not send the request to the solver (default: false).

See [kelheim-v3.0-25pct.rssched_request_config.xlsx](integration-test/input/de/kelheim/kelheim-v3.0/25pct/kelheim-v3.0-25pct.rssched_request_config.xlsx)
for reference of a request configuration.

**Note**: To be able to run the integration test using the command line app, first execute the integration tests, which
downloads the needed matsim run outputs, see the paragraph **Testing** below.

### Using Client in Java

Set up the `RsschedRequestClient` and send a request to the rolling stock scheduling solver service using
the `RsschedRequestConfig` to configure it. Optionally implement a transit line filter strategy.

Expand All @@ -39,7 +65,7 @@ import ch.sbb.rssched.client.RsschedMatsimClient;
import ch.sbb.rssched.client.config.RsschedRequestConfig;
import ch.sbb.rssched.client.dto.response.Response;

public class Example {
public class RunExample {

private static final String SCHEDULER_BASE_URL = "http://localhost";
private static final int SCHEDULER_PORT = 3000;
Expand All @@ -49,6 +75,7 @@ public class Example {
.setInputDirectory("path/to/input/directory")
.setOutputDirectory("path/to/output/directory")
.setRunId("runId")
.setInstanceId("rss001")
// optionally set transit line filter, default is no filtering
.setFilterStrategy(scenario -> {
// implementation...
Expand All @@ -65,17 +92,44 @@ public class Example {
}
```

Alternatively use an XLSX file to configure the request to the solver,
see [`RunExample`](src/main/java/ch/sbb/rssched/RunExample.java)
and [`request_config.xlsx`](src/test/resources/ch/sbb/rssched/client/config/request_config.xlsx).
Alternatively use an Excel file to configure the request to the solver:

```java
import ch.sbb.rssched.client.RsschedMatsimClient;
import ch.sbb.rssched.client.config.RsschedRequestConfig;
import ch.sbb.rssched.client.config.RsschedRequestConfigReader;
import ch.sbb.rssched.client.dto.response.Response;

import java.io.IOException;

public class RunExample {

private static final String REQUEST_CONFIG_XLSX = "rssched_request_config.xlsx";
private static final String SCHEDULER_BASE_URL = "http://localhost";
private static final int SCHEDULER_PORT = 3000;

public static void main(String[] args) throws IOException {
RsschedRequestConfig config = new RsschedRequestConfigReader().readExcelFile(REQUEST_CONFIG_XLSX);
RsschedMatsimClient client = new RsschedMatsimClient(SCHEDULER_BASE_URL, SCHEDULER_PORT);
Response response = client.process(config);
}

}
```

**Note:** There is a hard limit of 500 locations per instance, since the deadhead trip matrix grows exponentially.

## Testing

Run the unit tests:

```sh
mvn test
```

Run the integration test to see the pipeline in action (needs a running solver on localhost and port 3000):

```shell
```sh
mvn verify -Dit.test=RsschedMatsimClientIT
```

Expand Down

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
14 changes: 14 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@
<version>5.2.5</version>
</dependency>

<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.8.0</version>
</dependency>

</dependencies>

<build>
Expand Down Expand Up @@ -142,6 +148,14 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<mainClass>ch.sbb.rssched.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
120 changes: 120 additions & 0 deletions src/main/java/ch/sbb/rssched/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package ch.sbb.rssched;

import ch.sbb.rssched.client.RsschedMatsimClient;
import ch.sbb.rssched.client.RsschedMatsimRequestGenerator;
import ch.sbb.rssched.client.config.RsschedRequestConfig;
import ch.sbb.rssched.client.config.RsschedRequestConfigReader;
import ch.sbb.rssched.client.dto.response.Response;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

import java.io.IOException;

/**
* Read the request configuration from an Excel file and optionally send the request to the solver using the
* RsschedMatsimClient.
* <p>
* <b>Usage:</b>
* <pre>
* {@code ./rssched-matsim-client <config_file> -h / --host <host> -p / --port <port> -d / --dry-run}
* </pre>
* <p>
* If {@code -d / --dry-run} is present, the request is not sent to the solver.
* <p>
* <b>Required:</b>
* <ul>
* <li>{@code config_file}: Path to the Excel configuration file</li>
* </ul>
* <p>
* <b>Optional:</b>
* <ul>
* <li>{@code -h / --host}: Scheduler base URL (default: "localhost")</li>
* <li>{@code -p / --port}: Scheduler port (default: 3000)</li>
* <li>{@code -d / --dry-run}: If present, do not send the request to the solver (default: false)</li>
* </ul>
* <p>
* <b>Example with maven:</b>
* <pre>
* {@code mvn exec:java -Dexec.args="path_to_file.xlsx -h localhost -p 3000 -d"}
* </pre>
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Application {

// TODO: Export also RSSched configuration excel to output folder

public static final String APP_CMD_SYNTAX = "rssched-matsim-client <config_file>";
public static final String DEFAULT_HOST = "http://localhost";
public static final String DEFAULT_PORT = "3000";

public static void main(String[] args) {
try {
runApplication(args);
} catch (IOException | RuntimeException e) {
System.err.println(e.getMessage());
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(APP_CMD_SYNTAX, initOptions());
System.exit(1);
}
}

static void runApplication(String[] args) throws IOException {
Options options = initOptions();

CommandLineParser parser = new DefaultParser();
CommandLine cmd;

try {
cmd = parser.parse(options, args);
} catch (ParseException e) {
throw new RuntimeException(e.getMessage());
}

String[] remainingArgs = cmd.getArgs();
if (remainingArgs.length < 1) {
throw new RuntimeException("Missing required argument: config_file");
}

String requestConfigXlsx = remainingArgs[0];
String schedulerBaseUrl = cmd.getOptionValue("host", DEFAULT_HOST);
int schedulerPort = Integer.parseInt(cmd.getOptionValue("port", DEFAULT_PORT));
boolean sendToSolver = !cmd.hasOption("dry-run");

RsschedRequestConfig config = new RsschedRequestConfigReader().readExcelFile(requestConfigXlsx);

if (sendToSolver) {
// create request and send to solver
RsschedMatsimClient client = new RsschedMatsimClient(schedulerBaseUrl, schedulerPort);
Response response = client.process(config);
System.out.println(response.getInfo());
} else {
// only create request and export as JSON
new RsschedMatsimRequestGenerator().process(config);
}
}

private static Options initOptions() {
Options options = new Options();

Option hostOption = new Option("h", "host", true, "Scheduler base URL");
hostOption.setRequired(false);
options.addOption(hostOption);

Option portOption = new Option("p", "port", true, "Scheduler port");
portOption.setRequired(false);
options.addOption(portOption);

Option dryRunOption = new Option("d", "dry-run", false, "Dry run (do not send the request to the solver)");
dryRunOption.setRequired(false);
options.addOption(dryRunOption);

return options;
}
}
27 changes: 0 additions & 27 deletions src/main/java/ch/sbb/rssched/RunExample.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ch.sbb.rssched.client;

import ch.sbb.rssched.client.config.RsschedRequestConfig;
import ch.sbb.rssched.client.dto.request.Request;
import ch.sbb.rssched.client.pipeline.request.RequestPipeline;

import java.util.concurrent.atomic.AtomicReference;

/**
* Generates a rolling stock scheduling solver request from a MATSim run output and exports it to a JSON file.
*/
public class RsschedMatsimRequestGenerator {

public Request process(RsschedRequestConfig config) {
AtomicReference<Request> request = new AtomicReference<>();

var pipeline = new RequestPipeline(config);
pipeline.addSink(pipe -> request.set(pipe.getRequest()));
pipeline.run();

return request.get();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

import ch.sbb.rssched.client.config.selection.FilterStrategy;
import ch.sbb.rssched.client.config.selection.NoFilterStrategy;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -30,6 +36,7 @@ public class RsschedRequestConfig {
private final Shunting shunting = new Shunting();
private final Maintenance maintenance = new Maintenance();
private final Costs costs = new Costs();
private String instanceId;
private String runId;
private String inputDirectory;
private String outputDirectory;
Expand All @@ -38,6 +45,15 @@ public static Builder builder() {
return new Builder();
}

public String toJSON() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // Ensure dates are written in ISO 8601 format
mapper.enable(SerializationFeature.INDENT_OUTPUT);
return mapper.writeValueAsString(this);
}

/**
* Builder class for constructing a RequestConfig instance with customized settings. Allows addition of depots,
* shunting locations, and maintenance slots.
Expand All @@ -48,6 +64,11 @@ public static class Builder {
private final Set<String> depotLocations = new HashSet<>();
private final Map<String, Depot.Facility> depots = new HashMap<>();

public Builder setInstanceId(String instanceId) {
config.instanceId = instanceId;
return this;
}

public Builder setRunId(String runId) {
config.runId = runId;
return this;
Expand Down Expand Up @@ -111,9 +132,9 @@ public Builder addMaintenanceSlot(String id, String locationId, LocalDateTime st
* @return The fully configured RequestConfig instance.
*/
public RsschedRequestConfig buildWithDefaults() {
if (config.runId == null || config.inputDirectory == null || config.outputDirectory == null) {
if (config.instanceId == null || config.runId == null || config.inputDirectory == null || config.outputDirectory == null) {
throw new IllegalStateException(
"Mandatory fields (runId, inputDirectory, outputDirectory) must be set.");
"Mandatory fields (instanceId, runId, inputDirectory, outputDirectory) must be set.");
}
return config;
}
Expand All @@ -132,10 +153,13 @@ public static class Global {
* Note: The transit vehicle type ids must match / exist in the matsim scenario.
*/
private final Set<VehicleType> vehicleTypes = new HashSet<>();

/**
* The filter strategy to filter transit lines of interest, default is no filter.
*/
@JsonIgnore
private FilterStrategy filterStrategy = new NoFilterStrategy();

/**
* The sample size of the run, needed to scale to 100% for the demand.
*/
Expand All @@ -146,6 +170,12 @@ public static class Global {
*/
private double deadHeadTripSpeedLimit = 90 / 3.6;

/**
* The factor that is applied to travel the beeline distance at speed limit, if no path in the network is found
* for the dead head trip between two locations.
*/
private double deadHeadTripBeelineDistanceFactor = 5;

/**
* Allow deadhead trips?
*/
Expand Down
Loading

0 comments on commit df82385

Please sign in to comment.