Skip to content

Commit

Permalink
clarify use of collect with filter (fixes #46)
Browse files Browse the repository at this point in the history
  • Loading branch information
holgerbrandl committed Nov 4, 2023
1 parent 3a24453 commit eefc65a
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 101 deletions.
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dependencies {

api("org.json:json:20230227") // because version 20220924 is reported as vulerable
api("com.github.holgerbrandl:jsonbuilder:0.10")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")

// api("io.github.microutils:kotlin-logging:1.12.5")
// api("org.slf4j:slf4j-simple:1.7.32")
Expand All @@ -45,7 +45,7 @@ dependencies {
api("io.github.oshai:kotlin-logging-jvm:5.1.0")

testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.9.20")
testImplementation("io.kotest:kotest-assertions-core:5.5.5")
testImplementation("io.kotest:kotest-assertions-core:\t5.7.2")

// **TODO** move to api to require users to pull it in if needed
implementation("com.github.holgerbrandl:krangl:0.18.4") // must needed for kravis
Expand Down
3 changes: 0 additions & 3 deletions docs/userguide/docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@ To disable all metrics and to minimize internal event logging, the user can run

The same mechanism applies also fine-tune the internal [event logging](events.md). By disabling some - not-needed for production - events, simulation performance can be improved significantly.

The user can also register her own `TrackConfig` implementations using the factory. See [here](https://github.com/holgerbrandl/kalasim/blob/4f284e6f52ab9ab2f09b6bf5331f4fd413476702/src/test/kotlin/org/kalasim/test/ComponentTests.kt#L134-L134) for simple example.



## Save and Load Simulations

Expand Down
1 change: 1 addition & 0 deletions docs/userguide/docs/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Minor improvements
* [#51](https://github.com/holgerbrandl/kscript/issues/51) Added `description` for better readiability when supepending exeuction for simulatoin states using [`wait()`](component.md#wait)
* [#56](https://github.com/holgerbrandl/kalasim/issues/56) Improved support for [duration distributions](basics.md#duration-distributions)
* Expose `Environment.getOrNull<T>()` from [koin](https://github.com/InsertKoinIO/koin/issues/182) to check for presence of registered dependencies in simulation environment
* [#46](https://github.com/holgerbrandl/kalasim/issues/46) clarify use of collect with filter

Starting with this release we have switched to calendar versioning for better clarity regarding our release density, timing and schedule.

Expand Down
121 changes: 66 additions & 55 deletions docs/userguide/docs/events.md
Original file line number Diff line number Diff line change
@@ -1,76 +1,42 @@
# Events

To analyze state changes in a simulation model, we may want to monitor [component](component.md) creation, the [event queue](basics.md#event-queue), or the [interplay](component.md#process-interaction) between simulation entities. We may want to trace which process caused an event, or which processes waited for resource. Or a model may require other custom state change events to be monitored.
Every simulation includes an internal _event bus_ to provide another way to enable connectivity between simulation [components](component.md). Components can use `log(event)` to publish to the event bus from their [process definitions](component.md#process-definition).

`kalasim` internally triggers a rich set of built-int events
Also, events can be used to study dynamics in a simulation model. We may want to monitor [component](component.md) creation, the [event queue](basics.md#event-queue), or the [interplay](component.md#process-interaction) between simulation entities, or custom business dependent events of interest. We may want to trace which process caused an event, or which processes waited for resource. Or a model may require other custom state change events to be monitored.

* [Interactions](component.md#process-interaction) via `InteractionEvent`
* Entity creation via `EntityCreatedEvent`
* Resource requests, see [resource events](resource.md#events).

![](event_hierarchy.png)
## How to create an event?

In addition, it also allows for custom event types that can be triggered with `log()` in [process definitions](component.md#process-definition)

```kotlin
class MyEvent(time : SimTime) : Event(time)
To create a custom event type, we need to subcalss `org.kalasim.Event`. Events can be published to the internal event bus using `log()` in [process definitions](component.md#process-definition). Here's a simple [example](../../../src/test/kotlin/org/kalasim/examples/api/CustomEvent.kts)

object : Component() {
override fun process() = sequence {
//... a great history
log(MyEvent(now))
//... a promising future
}
}
```kotlin hl_lines="1000"
{!api/CustomEvent.kts!}
```

In this example, we have created custom simulation event type `MyEvent` which stores some additional context detail about the process. This approach is very common: By using custom event types when building process models with `kalasim`, state changes can be consumed very selectively for analysis and visualization.

## How to listen to events?

The event log is modelled as a sequence of `org.kalasim.Event`s that can be consumed with one more multiple `org.kalasim.EventListener`s. The classical publish-subscribe pattern is used here. Consumers can easily route events into such as consoles, files, rest-endpoints, databases, or in-place-analytics.
The event log can be consumed with one more multiple `org.kalasim.EventListener`s. The classical publish-subscribe pattern is used here. Consumers can easily route events into arbitrary sinks such as consoles, files, rest-endpoints, and databases, or perform in-place-analytics.

To get started, we can register new event handlers with `addEventListener(org.kalasim.EventListener)`. Since an `EventListener` is modelled as a [functional interface](https://kotlinlang.org/docs/fun-interfaces.html), the syntax is very concise and optionally supports generics:
We can register a event handlers with `addEventListener(org.kalasim.EventListener)`. Since an `EventListener` is modelled as a [functional interface](https://kotlinlang.org/docs/fun-interfaces.html), the syntax is very concise and optionally supports generics:

```kotlin
createSimulation {
// register to all events
addEventListener{ it: Event -> println(it)}

addEventListener{ it: MyEvent -> println(it)}

// ... or without the lambda arument just with
addEventListener<MyEvent>{ println(it)}

// register listener only for resource-events
addEventListener<ResourceEvent>{ it: ResourceEvent -> println(it)}
}
```

Event listener implementations typically do not want to consume all events but filter for specific types or simulation entities. This filtering can be implemented in the listener or by providing a the type of interest, when adding the listener.

```kotlin hl_lines="1000"
{!api/CustomEvent.kts!}
```

In this example, we have created custom simulation event type. This approach is very common: By using custom event types when building process models with `kalasim` state changes can be consumed very selectively in analysis and visualization.

## Component Logger

There are a few provided event listeners, most notable the built-in component logger. With component logging being enabled, kalasim will print a tabular listing of component state changes. Example:

```
time current component component action info
--------- ------------------------ ------------------------ ----------- -----------------------------
.00 main DATA create
.00 main
.00 Car.1 DATA create
.00 Car.1 DATA activate
.00 main CURRENT run +5.0
.00 Car.1
.00 Car.1 CURRENT hold +1.0
1.00 Car.1 CURRENT
1.00 Car.1 DATA ended
5.00 main
Process finished with exit code 0
```

Console logging is not active by default as it would considerably slow down larger simulations, and but must be [enabled](basics.md#configuring-a-simulation) when creating a simulation.

!!!note
The user can change the width of individual columns with `ConsoleTraceLogger.setColumnWidth()`

## Event Collector

Expand All @@ -82,7 +48,12 @@ class MyEvent(time : SimTime) : Event(time)
// run the sim which create many events including some MyEvents
env.run()

val myEvents :List<MyEvent> = eventCollector<MyEvent>()
val myEvents :List<MyEvent> = collect<MyEvent>()

// or collect with an additional filter condition
val myFilteredEvents :List<MyEvent> = collect<MyEvent> {
it.toString().startsWith("Foo")
}

// e.g. save them into a csv file with krangl
myEvents.asDataFrame().writeCsv(File("my_events.csv"))
Expand All @@ -107,17 +78,57 @@ Sometimes, events can not be consumed in the simulation thread, but must be proc
//{!analysis/LogChannelConsumerDsl.kts!}
```

In the example, we can think of a channel as a pipe between two coroutines. For details see the great article [_Kotlin: Diving in to Coroutines and Channels_](
In the example, we can think of a channel as a pipe between two coroutines. For details see the great article [_Kotlin: Diving in to Coroutines and Channels_](
https://proandroiddev.com/kotlin-coroutines-channels-csp-android-db441400965f).

## Logging Configuration

Typically, only some types of event logging are required in a given simulation. To optimize simulation performance, the engine allows to suppress selectively per event type and simulation entity. This is configured via [tracking policy factory](advanced.md#continuous-simulation)
## Internal Events


`kalasim` is using the event-bus extensively to publish a rich set of built-int events.

* [Interactions](component.md#process-interaction) via `InteractionEvent`
* Entity creation via `EntityCreatedEvent`
* Resource requests, see [resource events](resource.md#events).

![](event_hierarchy.png)

To speed up simulations, internal events can be [disabled](advanced.md#continuous-simulation).

### Component Logger

For internal interaction events, the library provides a built-in textual logger. With component logging being enabled, `kalasim` will print a tabular listing of component state changes and interactions. Example:

```
time current component component action info
--------- ------------------------ ------------------------ ----------- -----------------------------
.00 main DATA create
.00 main
.00 Car.1 DATA create
.00 Car.1 DATA activate
.00 main CURRENT run +5.0
.00 Car.1
.00 Car.1 CURRENT hold +1.0
1.00 Car.1 CURRENT
1.00 Car.1 DATA ended
5.00 main
Process finished with exit code 0
```

Console logging is not active by default as it would considerably slow down larger simulations. It can be [enabled](basics.md#configuring-a-simulation) when creating a simulation.

```kotlin
createSimuation(enableComponentLogger = true){
// some great sim in here!!
}
```

!!!note
The user can change the width of individual columns with `ConsoleTraceLogger.setColumnWidth()`

## Logging Framework Support

It's very easy to also log kalasim events via another logging library such as [log4j](https://logging.apache.org/log4j), [https://logging.apache.org/log4j/2.x/](https://www.slf4j.org), [kotlin-logging](https://github.com/MicroUtils/kotlin-logging) or the jdk-bundled [util-logger](https://docs.oracle.com/javase/7/docs/api/java/util/logging/Logger.html). This is how it works:
It's very easy to also log `kalasim` events via another logging library such as [log4j](https://logging.apache.org/log4j), [https://logging.apache.org/log4j/2.x/](https://www.slf4j.org), [kotlin-logging](https://github.com/MicroUtils/kotlin-logging) or the jdk-bundled [util-logger](https://docs.oracle.com/javase/7/docs/api/java/util/logging/Logger.html). This is how it works:

```kotlin
//{!api/LoggingAdapter.kts!}
Expand Down
31 changes: 31 additions & 0 deletions docs/userguide/docs/examples/shipyard/shipyard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Bill of Materials (BOM) in Ship Final Assembly: A Key Component of Efficient Production

## Introduction

The production process of complex structures such as ships involves meticulous planning, precision, and coordination of various components and materials. A crucial tool in this process is the Bill of Materials (BOM), which serves as a comprehensive document that outlines the list of components, materials, and parts required for the final assembly of a ship. In this article, we will explore how BOMs are used in ship final assembly, highlighting their importance in ensuring a smooth and efficient production process.

### What is a Bill of Materials (BOM)?

A Bill of Materials (BOM) is a structured list of all the items, components, and materials necessary to manufacture a product or assemble a final product, like a ship. It details each part's name, quantity, specifications, and the relationship between them. BOMs are crucial for coordinating various aspects of production, from sourcing materials to managing inventory and tracking costs.

### The Role of BOMs in Ship Final Assembly

Inventory Management: BOMs help shipbuilders keep track of all the components needed for assembly. This allows for efficient inventory management, ensuring that the right materials are available when needed, minimizing delays in production.

Quality Assurance: BOMs also include specifications and quality requirements for each component. This ensures that only the approved parts are used, reducing the likelihood of defects or safety issues in the final product.

Cost Estimation: By providing a detailed list of materials and components, BOMs are invaluable in cost estimation and budgeting. They allow shipbuilders to calculate the overall production costs accurately, ensuring that the project remains within budget.

Sourcing and Procurement: BOMs are used to create purchase orders for required materials and components. This helps streamline the procurement process, ensuring that the right parts are ordered in the correct quantities and from approved suppliers.

Assembly Planning: BOMs serve as a roadmap for the assembly process. They help in organizing the assembly line and ensuring that workers have access to the necessary components in the right order, reducing assembly time and improving efficiency.

### Example: Ship Final Assembly

Let's consider the assembly of a cargo ship as an example. The ship's BOM would include a detailed list of all components, such as the hull, engines, electrical systems, navigation equipment, and more. Each of these components would have its own sub-BOMs, breaking down further into individual parts. For instance, the electrical system's sub-BOM might include cables, switches, and circuit boards, specifying the quantity, specifications, and suppliers for each.

By having a well-structured BOM for the cargo ship, the shipbuilder can ensure that all components are available on time, that quality standards are met, and that the assembly process is efficient. This not only reduces the production timeline but also increases the overall quality and safety of the final product.

### Conclusion

Bill of Materials (BOMs) are an essential tool in the production process, especially in the complex field of ship assembly. They provide a detailed and organized overview of the materials and components required for final assembly, allowing for efficient management of inventory, quality assurance, cost estimation, and assembly planning. In shipbuilding, BOMs play a crucial role in ensuring that the final product is not only completed on time but also meets the highest quality and safety standards.
22 changes: 1 addition & 21 deletions src/main/kotlin/org/kalasim/Component.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,27 +93,6 @@ open class TickedComponent(
) : Component(name, at, delay, priority, process, koin, trackingConfig) {


/**
* Hold the component.
*
* For `hold` contract see [user manual](https://www.kalasim.org/component/#hold)
*
* @param duration Time to hold.
* @param priority If a component has the same time on the event list, this component is sorted according to
* the priority. An event with a higher priority will be scheduled first.
*/
// @OptIn(AmbiguousDuration::class)
// suspend fun SequenceScope<Component>.hold(
// duration: Number? = null,
// description: String? = null,
// until: Instant? = null,
// priority: Priority = NORMAL,
// urgent: Boolean = false
// ) = yieldCurrent {
// this@TickedComponent.hold(duration?.toDuration(), description, until?.let{TickTime(it)}, priority, urgent)
// }


/**
* Hold the component.
*
Expand Down Expand Up @@ -1543,6 +1522,7 @@ open class Component(
predicate: (T) -> Boolean
) = wait(
StateRequest(state, predicate = predicate, priority = triggerPriority),
description = description,
failPriority = failPriority,
failAt = failAt,
failDelay = failDelay,
Expand Down
8 changes: 7 additions & 1 deletion src/main/kotlin/org/kalasim/Environment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,16 @@ fun declareDependencies(
fun KoinModule.createSimulation(
/** The start time of the simulation model. Defaults to 1970-01-01T00:00:00Z following the convention of kotlin.time.Instant.*/
startDate: SimTime = SimTime.fromEpochMilliseconds(0),
/** If enabled, it will render a tabular view of recorded interaction, state change and resource events. */
enableComponentLogger: Boolean = false,
useCustomKoin: Boolean = false,
/** The duration unit of this environment. Every tick corresponds to a unit duration. See https://www.kalasim.org/basics/#running-a-simulation */
durationUnit: DurationUnit = MINUTES,
randomSeed: Int = DEFAULT_SEED,
builder: Environment.() -> Unit,
): Environment = createSimulation(
startDate = startDate,
enableComponentLogger=enableComponentLogger,
dependencies = this,
useCustomKoin = useCustomKoin,
tickDurationUnit = durationUnit,
Expand All @@ -66,6 +69,8 @@ fun KoinModule.createSimulation(
fun createSimulation(
/** The start time of the simulation model. Defaults to 1970-01-01T00:00:00Z following the convention of kotlin.time.Instant.*/
startDate: SimTime = SimTime.fromEpochMilliseconds(0),
/** If enabled, it will render a tabular view of recorded interaction, state change and resource events. */
enableComponentLogger: Boolean = false,
dependencies: KoinModule? = null,
useCustomKoin: Boolean = false,
/** The duration unit of this environment. Every tick corresponds to a unit duration. See https://www.kalasim.org/basics/#running-a-simulation */
Expand All @@ -74,6 +79,7 @@ fun createSimulation(
builder: Environment.() -> Unit,
): Environment = Environment(
startDate = startDate,
enableComponentLogger= enableComponentLogger,
tickDurationUnit = tickDurationUnit,
dependencies = dependencies,
koin = if(useCustomKoin) koinApplication { }.koin else null,
Expand Down Expand Up @@ -104,7 +110,7 @@ fun main() {
open class Environment(
/** The start time of the simulation model. Defaults to 1970-01-01T00:00:00Z following the convention of kotlin.time.Instant.*/
val startDate: SimTime = SimTime.fromEpochMilliseconds(0),
/** If enabled, it will render a tabular view of recorded interaction and resource events. */
/** If enabled, it will render a tabular view of recorded interaction, state change and resource events. */
enableComponentLogger: Boolean = false,

/** Measure the compute time per tick as function of time. For details see https://www.kalasim.org/advanced/#operational-control */
Expand Down
Loading

0 comments on commit eefc65a

Please sign in to comment.