Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
.project
.settings
.vscode
.history
target
dependency-reduced-pom.xml
*.iml
*.idea
simulation_output
.vscode
._*
bavaria/data/*
nohup.out
110 changes: 109 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,112 @@
# eqasim-java

# How to run a MATSim simulation

## 🚀 Running the MATSim Simulation

This repo on the LRZ gitlab uses a pre-configured setup from eqasim's simulation repository.

## ✅ Prerequisites

1. Make sure you’ve completed all steps for generating the synthetic population. You find examples for the population synthesis output files on the internal drives: `\\10.152.34.30\Austausch\MATSim Populations`
2. Additional configuration details are available here:
👉 [https://github.com/eqasim-org/bavaria/blob/development-minga/docs/simulation.md](https://github.com/eqasim-org/bavaria/blob/development-minga/docs/simulation.md)

---

### 1️⃣ Clone the Simulation Repository

Use this repository and branch:

- **Repository:**
🔗 [https://gitlab.lrz.de/tum-vt/minga-eqasim-simulation](https://gitlab.lrz.de/tum-vt/minga-eqasim-simulation)

- **Branch:** `bavaria-mode-choice`
🔗 [https://gitlab.lrz.de/tum-vt/minga-eqasim-simulation/tree/bavaria-mode-choice](https://gitlab.lrz.de/tum-vt/minga-eqasim-simulation/tree/bavaria-mode-choice)

Clone and check out the correct branch:

```bash
git clone https://gitlab.lrz.de/tum-vt/minga-eqasim-simulation.git
cd eqasim-java
git checkout bavaria-mode-choice
```

---

### 2️⃣ (Optional) Create Your Own Branch Based on `bavaria-mode-choice`

If you want to make changes or run your own experiments:

```bash
git checkout -b my-example-experiment
git push -u origin my-example-experiment
```

This creates your own branch based on `bavaria-mode-choice` and sets up tracking so that future pushes and pulls work seamlessly.

---

### 3️⃣ Prepare the `data` Folder

In the `bavaria` module, create a `data` folder, containing the folder `munich` and copy your synthetic population and network files there:

```bash
mkdir -p bavaria/data/munich
```
Copy into this folder all files created in the synthetic population, such as the config, network file, etc. Only xml files are required! csv files are outputs of the MATSim simulation and are not used as inputs for the simulation.

Ensure that you use a `config.xml` in which the eqasim:raptor parameters are set as follows:
```
<module name="eqasim:raptor" >
<param name="perTransfer_u" value="-0.5441109013512305" /> <!-- IdF: -0.5441109013512305 -->
<param name="travelTimeBus_u_h" value="-2.162021" /> <!-- IdF: -2.835025304050246 -->
<param name="travelTimeOther_u_h" value="-2.162021" /> <!-- IdF: -2.835025304050246 -->
<param name="travelTimeRail_u_h" value="-2.162021" /> <!-- IdF: -1.4278139352278472 -->
<param name="travelTimeSubway_u_h" value="-2.162021" /> <!-- IdF: -1.0 -->
<param name="travelTimeTram_u_h" value="-2.162021" /> <!-- IdF: -3.199594607188756 -->
<param name="waitTime_u_h" value="-24.67436" /> <!-- IdF: -0.497984826174775 -->
<param name="walkTime_u_h" value="-4.172657" /> <!-- IdF: -3.8494071051697385 -->
</module>
```
You can use the config file that is used for calibration: `config_munich_calibrated.xml` from this repository.

---

### 4️⃣ Run the Simulation

First, build the project:
```bash
mvn clean package -Pstandalone --projects bavaria --also-make -DskipTests=true
```

This will generate a `bavaria-1.5.0.jar` file in the `bavaria/target` directory.

To run a single simulation, use:

```bash
nohup java -Xmx12G -cp bavaria/target/bavaria-1.5.0.jar org.eqasim.bavaria.RunSimulation --config-path bavaria/data/munich_0.1/munich_config.xml &> simulation_output.log &
```
If you are running the simulation on a cluster or machine with larger memory resources, consider increasing the Java heap space, e.g., by setting -Xmx120G.

If you want to run simulations for multiple random seeds, use the `RunSimulationsMultipleSeeds`, where you can specify the number of seeds, threads, and memory:
```bash
nohup java -cp bavaria/target/bavaria-1.5.0.jar org.eqasim.bavaria.RunSimulationsMultipleSeeds --seeds 3 --threads 12 --memory 60 > output.log 2>&1 &
```

This command works for all downsampled population sizes.
There’s no need to change the command based on population size — just ensure the corresponding population file is correctly referenced in the config.

If you want to run MATSim on the LRZ cluster (e.g., cm4_tiny), you need to ask Peter for an account. After setting up your account, you can do the following steps to run the simulation:
1. Go to Windows PowerShell (or similar) and use the command `ssh <your_user_name>@cool.hpc.lrz.de` to login.
2. Use the command `sbatch slurm_matsim_run_example.txt`. This file is part of this repo and can be copied.
3. `squeue -M cm4` tells you if your job is queued.
There are some restrictions on the LRZ cluster like the maximum of submitted jobs (currently 4), number of kernels etc. Find details to the LRZ cluster here: [https://doku.lrz.de/linux-cluster-10745672.html](https://doku.lrz.de/linux-cluster-10745672.html).

In the `slurm_matsim_run_example.txt`file, there are some arguments for the mode choice parameter setting. These are **not** required since they are already correctly set in the `BavariaModeParameters.java` file.



# eqasim-java [from the original readme]

![eqasim](docs/top.png "eqasim")

Expand Down
8 changes: 8 additions & 0 deletions bavaria/src/main/java/org/eqasim/bavaria/RunSimulation.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.util.Collections;
import java.util.Set;

import org.eqasim.bavaria.mode_choice.BavariaModeChoiceModule;
import org.eqasim.core.components.config.EqasimConfigGroup;
import org.eqasim.core.scenario.validation.VehiclesValidator;
import org.eqasim.core.simulation.vdf.VDFConfigGroup;
import org.eqasim.core.simulation.vdf.engine.VDFEngineConfigGroup;
Expand Down Expand Up @@ -48,6 +50,12 @@ static public void main(String[] args) throws ConfigurationException {
cmd.applyConfiguration(config);
VehiclesValidator.validate(config);

EqasimConfigGroup eqasimConfig = EqasimConfigGroup.get(config);
if (!eqasimConfig.getEstimators().get("walk").equals(BavariaModeChoiceModule.WALK_ESTIMATOR_NAME)) {
throw new IllegalArgumentException(
"Config needs to use bavariaWalk for mode choice. Please define BavariaWalkUtilityEstimator in estimators for mode walk.");
}

Scenario scenario = ScenarioUtils.createScenario(config);
configurator.configureScenario(scenario);
ScenarioUtils.loadScenario(scenario);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package org.eqasim.bavaria;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
* Runs multiple MATSim simulations for Munich using different random seeds, supporting parallel execution and automated output management.
*
* The class parses command line arguments to configure the number of seeds, thread count, and memory allocation.
* It sets up the necessary directories for Munich, manages concurrent simulation runs using a thread pool, and ensures that output directories are prepared and cleaned as needed.
*
* Example usage:
* nohup java -cp bavaria/target/bavaria-1.5.0.jar org.eqasim.bavaria.RunSimulationsMultipleSeeds > output.log 2>&1 &
* nohup java -cp bavaria/target/bavaria-1.5.0.jar org.eqasim.bavaria.RunSimulationsMultipleSeeds --seeds 3 --threads 12 --memory 60 > output.log 2>&1 &
*
* Note: After making changes to this class, recompile the project with:
* mvn clean package -Pstandalone --projects bavaria --also-make -DskipTests=true
*/

public class RunSimulationsMultipleSeeds extends SimulationRunnerBase {
private static final Logger LOGGER = Logger.getLogger(RunSimulationsMultipleSeeds.class.getName());

static public void main(String[] args) throws Exception {
Config config;
try {
config = parseConfig(args);
} catch (IllegalArgumentException e) {
LOGGER.severe(e.getMessage());
printUsage();
System.exit(1);
return; // Never reached, but needed for compiler
}

LOGGER.info("Running simulation with configuration: " + config);

// Configuration settings
String configPath = "munich_config.xml";
String workingDirectory = "bavaria/data/munich/";

LOGGER.info("Starting simulation with the following settings:");
LOGGER.info("Configuration file: " + configPath);
LOGGER.info("Working directory: " + workingDirectory);

// Create a fixed thread pool with specified number of threads
ExecutorService executor = Executors.newFixedThreadPool(config.threads);
LOGGER.info("Created thread pool with " + config.threads + " threads");

final String networkFile = "munich_network.xml.gz";
LOGGER.info("Using network file: " + networkFile);

final int currentSeed = config.numSeeds;
final String seedOutputDirectory = "bavaria/data/munich/output/seed_" + currentSeed + "/";
LOGGER.info("Output for seed " + currentSeed + " will be written to: " + seedOutputDirectory);

// Check if the output file exists for the current seed
boolean seedSimulationRanSuccessfully = checkIfFileExists(seedOutputDirectory, "output_events.xml.gz");
LOGGER.info("Checking if output exists for seed " + currentSeed + ": " + seedSimulationRanSuccessfully);

if (!seedSimulationRanSuccessfully) {
try {
if (outputDirectoryExists(seedOutputDirectory)) {
createAndEmptyDirectory(seedOutputDirectory);
LOGGER.info("Emptied output directory while preserving log files: " + seedOutputDirectory);
} else {
Files.createDirectories(Paths.get(seedOutputDirectory));
LOGGER.info("Created output directory: " + seedOutputDirectory);
}

// Submit task for the current seed
executor.submit(() -> {
LOGGER.info("Starting simulation task for: " + networkFile + " with seed " + currentSeed);
try {
runSimulation(configPath, networkFile, seedOutputDirectory, workingDirectory, args, currentSeed,
config.threads, config.threads, config.memory, true);
LOGGER.info("Completed simulation for: " + networkFile + " with seed " + currentSeed);
// If you want to keep only the three files output_links.csv.gz, output_events.xml.gz, and eqasim_trips.csv (for memory reasons), uncomment the following line.
// deleteUnwantedFiles(seedOutputDirectory);
// LOGGER.info("Deleted unwanted files for: " + networkFile + " with seed " + currentSeed);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOGGER.log(Level.SEVERE, "Simulation interrupted for: " + networkFile + " with seed " + currentSeed, e);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error in simulation for: " + networkFile + " with seed " + currentSeed, e);
}
});
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to setup output directory: " + seedOutputDirectory, e);
throw e;
}
} else {
LOGGER.info("Skipping simulation for seed " + currentSeed + " - output already exists in: " + seedOutputDirectory);
}

// Shutdown the executor
executor.shutdown();
try {
if (!executor.awaitTermination(300, TimeUnit.HOURS)) {
executor.shutdownNow();
if (!executor.awaitTermination(360, TimeUnit.SECONDS)) {
LOGGER.severe("Executor did not terminate properly");
}
}
} catch (InterruptedException ie) {
executor.shutdownNow();
Thread.currentThread().interrupt();
LOGGER.log(Level.SEVERE, "Executor was interrupted", ie);
}
LOGGER.info("All simulations completed");
}

/**
* Print usage instructions
*/
private static void printUsage() {
LOGGER.severe("Usage: java -cp bavaria/target/bavaria-1.5.0.jar org.eqasim.bavaria.RunSimulationsMultipleSeeds " +
"[--seeds <number_of_seeds>] [--threads <number_of_threads>] " +
"[--memory <memory_in_GB>]");
}

/**
* Configuration class to hold all simulation parameters
*/
private static class Config {
int numSeeds = 3; // Default to 3 seeds
int threads = 12; // Default to 12 threads
int memory = 120; // Default to 60GB

@Override
public String toString() {
return String.format("Config{numSeeds=%d, threads=%d, memory=%dGB}",
numSeeds, threads, memory);
}
}

/**
* Parse and validate command line arguments
* @param args Command line arguments
* @return Config object with validated parameters
* @throws IllegalArgumentException if required parameters are missing or invalid
*/
private static Config parseConfig(String[] args) {
Config config = new Config();

for (int i = 0; i < args.length; i++) {
if (args[i].equals("--seeds") && i + 1 < args.length) {
try {
config.numSeeds = Integer.parseInt(args[i + 1]);
if (config.numSeeds < 1) {
throw new NumberFormatException("Number of seeds must be positive");
}
i++; // Skip the next argument
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid number of seeds. Please provide a positive integer.");
}
} else if (args[i].equals("--threads") && i + 1 < args.length) {
try {
config.threads = Integer.parseInt(args[i + 1]);
if (config.threads < 1) {
throw new NumberFormatException("Number of threads must be positive");
}
i++; // Skip the next argument
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid number of threads. Please provide a positive integer.");
}
} else if (args[i].equals("--memory") && i + 1 < args.length) {
try {
config.memory = Integer.parseInt(args[i + 1]);
if (config.memory < 1) {
throw new NumberFormatException("Memory allocation must be positive");
}
i++; // Skip the next argument
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid memory allocation. Please provide a positive integer.");
}
}
}
return config;
}
}
Loading
Loading