Skip to content

Advanced Features ​

This guide covers advanced simulation features for debugging, analysis, and optimization.

Event Tracing ​

Event tracing provides detailed logging of event execution for debugging and analysis. Track the exact order of events, their timing, and priority handling.

Enabling Event Tracing ​

typescript
import { Simulation } from 'discrete-sim';

const sim = new Simulation();

// Enable event tracing
sim.enableEventTrace();

// Run simulation
sim.run(100);

// Get trace
const trace = sim.getEventTrace();

trace.forEach(entry => {
  console.log(`Event ${entry.eventId} at time ${entry.time} (priority ${entry.priority})`);
});

Event Trace Structure ​

Each trace entry contains:

typescript
interface EventTraceEntry {
  eventId: number;      // Unique event identifier
  time: number;         // Simulation time when executed
  priority: number;     // Event priority (lower = higher priority)
  executionOrder: number; // Order in which event was executed
}

Example: Debugging Event Order ​

typescript
const sim = new Simulation();
sim.enableEventTrace();

// Schedule events with different priorities
sim.schedule(10, () => console.log('Event A'), 0);
sim.schedule(10, () => console.log('Event B'), 1);
sim.schedule(10, () => console.log('Event C'), 0);

sim.run();

// Analyze execution order
const trace = sim.getEventTrace();
trace.forEach(entry => {
  console.log(`Order ${entry.executionOrder}: Event ${entry.eventId} at t=${entry.time} p=${entry.priority}`);
});

// Output:
// Order 1: Event 1 at t=10 p=0  (Event A - priority 0)
// Order 2: Event 3 at t=10 p=0  (Event C - priority 0, same priority as A)
// Order 3: Event 2 at t=10 p=1  (Event B - priority 1, lower priority)

Clearing Event Trace ​

typescript
// Clear trace to save memory
sim.clearEventTrace();

// Disable tracing
sim.disableEventTrace();

Use Cases ​

1. Debugging Race Conditions

typescript
sim.enableEventTrace();

function* process1() {
  console.log('P1 start');
  yield* timeout(0);  // Zero-delay event
  console.log('P1 end');
}

function* process2() {
  console.log('P2 start');
  yield* timeout(0);  // Zero-delay event
  console.log('P2 end');
}

sim.process(() => process1());
sim.process(() => process2());
sim.run();

// Check trace to see exact execution order

2. Performance Analysis

typescript
sim.enableEventTrace();
sim.run(1000);

const trace = sim.getEventTrace();
console.log(`Total events executed: ${trace.length}`);

// Events per time unit
const eventsPerTime = trace.reduce((acc, entry) => {
  acc[Math.floor(entry.time)] = (acc[Math.floor(entry.time)] || 0) + 1;
  return acc;
}, {});

console.log('Events per time unit:', eventsPerTime);

3. Priority Queue Verification

typescript
sim.enableEventTrace();

// Schedule events with priorities
for (let i = 0; i < 10; i++) {
  const priority = Math.floor(Math.random() * 5);
  sim.schedule(50, () => {}, priority);
}

sim.run();

// Verify priority ordering
const trace = sim.getEventTrace();
const timeSlice = trace.filter(e => e.time === 50);

console.log('Events at t=50:');
timeSlice.forEach(e => {
  console.log(`  Event ${e.eventId}: priority ${e.priority}`);
});

Warm-up Periods ​

Warm-up periods exclude initial transient behavior from statistics, providing accurate steady-state analysis.

Why Use Warm-up Periods? ​

Many simulations start in an unrealistic state:

  • Empty queues in a busy system
  • Full inventory in a stockout scenario
  • Idle servers that are normally busy

The warm-up period allows the simulation to reach steady state before collecting statistics.

Setting Warm-up Period ​

typescript
import { Simulation } from 'discrete-sim';

const sim = new Simulation();
const stats = sim.statistics;

// Exclude first 100 time units from statistics
stats.setWarmupPeriod(100);

// Run simulation
sim.run(1000);

// Statistics only include time 100-1000
const avgWait = stats.getAverage('wait-time');

Checking Warm-up Status ​

typescript
// Get warm-up end time
const warmupEnd = stats.getWarmupPeriod();
console.log(`Warm-up ends at ${warmupEnd}`);

// Check if still in warm-up
if (stats.isInWarmup()) {
  console.log(`Still in warm-up (now: ${sim.now}, warmup: ${warmupEnd})`);
}

Example: Queue System with Warm-up ​

typescript
function* customer(id: number, server: Resource) {
  const arrivalTime = sim.now;

  yield server.request();

  const waitTime = sim.now - arrivalTime;

  // This will be excluded if arrival was during warm-up
  stats.recordValue('wait-time', waitTime);

  yield* timeout(5);
  server.release();
}

const sim = new Simulation();
const stats = sim.statistics;
const server = new Resource(sim, 2, { name: 'Server' });

// Set warm-up period
stats.setWarmupPeriod(100);

// Generate arrivals
function* arrivalProcess(rng: Random) {
  let id = 0;
  while (sim.now < 1000) {
    sim.process(() => customer(id++, server));
    yield* timeout(rng.exponential(3));
  }
}

const rng = new Random(42);
sim.process(() => arrivalProcess(rng));

sim.run(1000);

// Statistics from time 100-1000 only
console.log(`Average wait time (steady state): ${stats.getAverage('wait-time').toFixed(2)}`);
console.log(`Server utilization (steady state): ${stats.getAverage('Server:utilization').toFixed(3)}`);

How Warm-up Affects Statistics ​

Statistic TypeWarm-up Behavior
CountersAll increments counted (no warm-up)
ValuesSamples during warm-up excluded
Time-weightedOnly time after warm-up counted
Resource statsOnly time after warm-up counted

Determining Warm-up Length ​

Use visual analysis or statistical methods:

typescript
// Method 1: Visual inspection
const sim = new Simulation();
const stats = sim.statistics;

function* monitor() {
  while (sim.now < 500) {
    console.log(`t=${sim.now.toFixed(0)}: queue=${server.queueLength}, util=${(server.inUse/server.capacity).toFixed(2)}`);
    yield* timeout(10);
  }
}

sim.process(() => monitor());
sim.run(500);

// Look for point where metrics stabilize
// Then set warm-up period to that point
stats.setWarmupPeriod(150);
typescript
// Method 2: Multiple replications
const warmupLengths = [0, 50, 100, 150, 200];
const results = [];

for (const warmup of warmupLengths) {
  const sim = new Simulation();
  sim.statistics.setWarmupPeriod(warmup);

  // Run simulation
  runSimulation(sim);

  results.push({
    warmup,
    avgWait: sim.statistics.getAverage('wait-time')
  });
}

// Choose warmup where results stabilize
console.log('Warmup analysis:');
results.forEach(r => {
  console.log(`  Warmup ${r.warmup}: avg wait = ${r.avgWait.toFixed(2)}`);
});

Configurable Condition Waiting ​

The waitFor helper supports customizable polling for condition-based waiting.

Basic Usage ​

typescript
import { waitFor } from 'discrete-sim';

function* process() {
  // Wait for condition to be true
  yield* waitFor(sim, () => resource.available > 0);

  // Condition is now true
  console.log('Resource is available!');
}

Configurable Polling ​

typescript
import { waitFor, ConditionTimeoutError } from 'discrete-sim';

function* process() {
  try {
    // Check every 2 time units, max 50 iterations
    yield* waitFor(
      sim,
      () => inventory.level > 100,
      {
        interval: 2,           // Check every 2 time units
        maxIterations: 50      // Timeout after 50 checks (100 time units)
      }
    );

    console.log('Inventory restocked!');
  } catch (error) {
    if (error instanceof ConditionTimeoutError) {
      console.log('Timeout waiting for inventory');
    }
  }
}

Options ​

typescript
interface WaitForOptions {
  interval?: number;       // Polling interval (default: 1)
  maxIterations?: number;  // Max polls before timeout (default: Infinity)
}

Use Cases ​

1. Wait for Resource Availability

typescript
function* process() {
  // Wait for at least 2 servers available
  yield* waitFor(sim, () => server.available >= 2);

  // Grab both servers
  yield server.request();
  yield server.request();

  // Use servers
  yield* timeout(10);

  server.release();
  server.release();
}

2. Wait for Inventory Threshold

typescript
function* restockingProcess(inventory: Buffer) {
  while (true) {
    // Wait for low inventory
    yield* waitFor(
      sim,
      () => inventory.level < 1000,
      { interval: 1, maxIterations: 1000 }
    );

    console.log(`Restocking (level: ${inventory.level})`);

    // Restock
    yield inventory.put(5000);
  }
}

3. Coordinated Conditions

typescript
function* coordinatedProcess() {
  // Wait for multiple conditions
  yield* waitFor(sim, () =>
    machineA.available > 0 &&
    machineB.available > 0 &&
    inventory.level >= 50
  );

  // All conditions met
  console.log('Starting coordinated operation');
}

Process Cleanup on Reset ​

When resetting a simulation, all active processes are automatically interrupted and cleaned up.

Reset Behavior ​

typescript
const sim = new Simulation();

function* longRunningProcess() {
  try {
    while (true) {
      yield* timeout(10);
      console.log(`Still running at ${sim.now}`);
    }
  } catch (error) {
    console.log('Process interrupted during reset');
    // Cleanup code here
  }
}

sim.process(() => longRunningProcess());

// Run for a while
sim.run(50);

// Reset interrupts all processes
sim.reset();

// Processes are automatically cleaned up
console.log('Simulation reset, all processes stopped');

Graceful Cleanup ​

typescript
function* processWithCleanup(resource: Resource) {
  try {
    yield resource.request();

    // Do work
    yield* timeout(10);

    resource.release();
  } catch (error) {
    // Always cleanup, even on interruption
    if (resource.inUse > 0) {
      resource.release();
    }
    console.log('Cleaned up resources before exit');
  }
}

Best Practices ​

Event Tracing ​

  1. Enable only when debugging - Tracing adds memory overhead
  2. Clear trace periodically - For long simulations
  3. Use for verification - Confirm priority queuing works correctly

Warm-up Periods ​

  1. Always use for steady-state analysis - Critical for accurate results
  2. Determine length empirically - Run pilot simulations
  3. Document your choice - Explain warm-up period selection
  4. Multiple replications - Verify results with different warm-up lengths

Condition Waiting ​

  1. Set reasonable timeouts - Prevent infinite loops
  2. Use appropriate intervals - Balance responsiveness vs overhead
  3. Handle timeout errors - Always catch ConditionTimeoutError

Process Cleanup ​

  1. Always use try-catch - Handle interruptions gracefully
  2. Release resources in finally - Ensure cleanup
  3. Test reset behavior - Verify processes clean up correctly

See Also ​

Released under the MIT License.