Skip to content

Commit 8e697b4

Browse files
committed
Refactor flow model. Include buffer
1 parent f8c122a commit 8e697b4

File tree

18 files changed

+412
-161
lines changed

18 files changed

+412
-161
lines changed

controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/ConstructorElement.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public interface StateContainer : ContextAware, CoroutineScope {
8383
}
8484
}
8585

86-
public interface Model: StateContainer
86+
public interface Model : StateContainer
8787

8888
/**
8989
* Run simulation using context simulation dispatcher
@@ -96,7 +96,8 @@ public suspend fun <M : Model> M.runSimulation(
9696
}
9797
}
9898

99-
public val StateContainer.states get() = constructorElements.filterIsInstance<StateConstructorElement<*>>().map { it.state }
99+
public val StateContainer.states
100+
get() = constructorElements.filterIsInstance<StateConstructorElement<*>>().map { it.state }
100101

101102
/**
102103
* Register a [state] in this container. The state is not registered as a device property if [this] is a [DeviceConstructor]
@@ -122,7 +123,7 @@ public fun <T : ModelConstructor> StateContainer.model(model: T): T {
122123
* Create and register a timer state.
123124
*/
124125
public fun StateContainer.timer(tick: Duration): TimerState =
125-
registerState(TimerState(context.request(ClockManager), tick))
126+
registerState(TimerState(context.plugins[ClockManager] ?: context.request(ClockManager), tick))
126127

127128
/**
128129
* Register a new timer and perform [block] on its change
@@ -181,6 +182,14 @@ public fun <T1, T2, R> StateContainer.combineState(
181182
transformation: (T1, T2) -> R,
182183
): DeviceState<R> = registerState(DeviceState.combine(first, second, transformation))
183184

185+
186+
public fun <T1, T2, T3, R> StateContainer.combineState(
187+
first: DeviceState<T1>,
188+
second: DeviceState<T2>,
189+
third: DeviceState<T3>,
190+
transformation: (T1, T2, T3) -> R,
191+
): DeviceState<R> = registerState(DeviceState.combine(first, second, third, transformation))
192+
184193
/**
185194
* Combines multiple device states into a single state by applying a transformation function.
186195
*

controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/TimerState.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ public class TimerState(
2323
initialValue: Instant = Instant.DISTANT_PAST,
2424
) : DeviceState<Instant> {
2525

26-
private val clock = MutableStateFlow(initialValue)
26+
private val time = MutableStateFlow(initialValue)
2727

2828
private val updateJob = clockManager.context.launch(clockManager.simulationDispatcher) {
2929
while (isActive) {
30-
clock.value = clockManager.clock.now()
30+
time.value = clockManager.clock.now()
3131
delay(tick)
3232
}
3333
}
3434

35-
override val valueFlow: Flow<Instant> get() = clock
35+
override val valueFlow: Flow<Instant> get() = time
3636

37-
override val value: Instant get() = clock.value
37+
override val value: Instant get() = time.value
3838

3939
override fun toString(): String = "TimerState(tick=$tick)"
4040
}

controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/devices/Drive.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import space.kscience.controls.constructor.MutableDeviceState
55
import space.kscience.controls.constructor.property
66
import space.kscience.controls.constructor.units.NewtonsMeters
77
import space.kscience.controls.constructor.units.Numeric
8-
import space.kscience.controls.constructor.units.numericalValue
8+
import space.kscience.controls.constructor.units.numeric
99
import space.kscience.dataforge.context.Context
1010
import space.kscience.dataforge.meta.MetaConverter
1111

@@ -15,5 +15,5 @@ public class Drive(
1515
context: Context,
1616
force: MutableDeviceState<Numeric<NewtonsMeters>> = MutableDeviceState(Numeric(0)),
1717
) : DeviceConstructor(context) {
18-
public val force: MutableDeviceState<Numeric<NewtonsMeters>> by property(MetaConverter.numericalValue(), force)
18+
public val force: MutableDeviceState<Numeric<NewtonsMeters>> by property(MetaConverter.numeric(), force)
1919
}

controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/devices/EncoderDevice.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import space.kscience.controls.constructor.DeviceState
55
import space.kscience.controls.constructor.property
66
import space.kscience.controls.constructor.units.Degrees
77
import space.kscience.controls.constructor.units.Numeric
8-
import space.kscience.controls.constructor.units.numericalValue
8+
import space.kscience.controls.constructor.units.numeric
99
import space.kscience.dataforge.context.Context
1010
import space.kscience.dataforge.meta.MetaConverter
1111

@@ -16,5 +16,5 @@ public class EncoderDevice(
1616
context: Context,
1717
position: DeviceState<Numeric<Degrees>>
1818
) : DeviceConstructor(context) {
19-
public val position: DeviceState<Numeric<Degrees>> by property(MetaConverter.numericalValue<Degrees>(), position)
19+
public val position: DeviceState<Numeric<Degrees>> by property(MetaConverter.numeric<Degrees>(), position)
2020
}

controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/devices/LinearDrive.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import space.kscience.controls.constructor.models.PidRegulator
66
import space.kscience.controls.constructor.units.Meters
77
import space.kscience.controls.constructor.units.NewtonsMeters
88
import space.kscience.controls.constructor.units.Numeric
9-
import space.kscience.controls.constructor.units.numericalValue
9+
import space.kscience.controls.constructor.units.numeric
1010
import space.kscience.dataforge.context.Context
1111
import space.kscience.dataforge.meta.Meta
1212
import space.kscience.dataforge.meta.MetaConverter
@@ -21,7 +21,7 @@ public class LinearDrive(
2121
meta: Meta = Meta.EMPTY,
2222
) : DeviceConstructor(context, meta) {
2323

24-
public val position: DeviceState<Numeric<Meters>> by property(MetaConverter.numericalValue(), position)
24+
public val position: DeviceState<Numeric<Meters>> by property(MetaConverter.numeric(), position)
2525

2626
public val drive: Drive by device(drive)
2727
public val pid: PidRegulator<Meters, NewtonsMeters> = model(

controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/models/flow/Connections.kt

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,124 @@
11
package space.kscience.controls.constructor.models.flow
22

3-
//public class ContinuousBuffer( context: Context,
4-
// public val algebra: AmountAlgebra<T>,
5-
// public val outputNames: Collection<String>,
6-
// private val joinManagementStrategy: JoinManagementStrategy = JoinManagementStrategy.PROPORTIONAL,
7-
//) : ModelConstructor(context), ContinuousProducerModel<T> {
8-
//}
3+
import kotlinx.coroutines.delay
4+
import space.kscience.controls.constructor.*
5+
import space.kscience.controls.constructor.units.*
6+
import space.kscience.dataforge.context.Context
7+
import kotlin.time.Duration
8+
import kotlin.time.Duration.Companion.seconds
9+
10+
/**
11+
* A simulation model that represents a continuous buffer for storing a quantity of a measurable unit.
12+
* The buffer dynamically responds to supply and consumption requests while ensuring constraints such as buffer capacity.
13+
*
14+
* @param U The unit of measurement associated with the buffer contents.
15+
* @param T The amount type representing the measurable quantity stored in the buffer.
16+
* @param context The simulation context in which the buffer operates.
17+
* @param algebra The algebraic operations to manipulate the amount type.
18+
* @param bufferCapacity The maximum capacity of the buffer as a device state.
19+
* @param supplyRequest A device state representing the amount requested to be added to the buffer.
20+
* @param consumerRequest A device state representing the amount requested to be consumed from the buffer.
21+
* @param initialLevel The initial quantity stored in the buffer.
22+
* @param timeStep The simulation time step for updating the buffer's state.
23+
*
24+
* The model tracks the current level of stored content, calculates remaining buffer space, and manages production and consumption dynamics.
25+
* It ensures the buffer's level stays within the bounds defined by its capacity.
26+
*/
27+
public class ContinuousBuffer<U : UnitsOfMeasurement, T : Amount<U>>(
28+
context: Context,
29+
public val algebra: AmountAlgebra<U, T>,
30+
public val bufferCapacity: DeviceState<Numeric<U>>,
31+
override val supplyRequest: LateBindDeviceState<T> = LateBindDeviceState(algebra.zero),
32+
override val consumerRequest: LateBindDeviceState<Numeric<U>> = LateBindDeviceState(Numeric.zero()),
33+
initialLevel: T = algebra.zero,
34+
timeStep: Duration = 1.seconds
35+
) : ModelConstructor(context), ContinuousProducerInterface<U, T>, ContinuousConsumerInterface<U, T> {
36+
37+
private val _content: MutableDeviceState<T> = MutableDeviceState(initialLevel)
38+
39+
public val content: DeviceState<T> get() = _content
40+
41+
init {
42+
registerState(bufferCapacity)
43+
registerState(content)
44+
registerState(supplyRequest)
45+
registerState(consumerRequest)
46+
}
47+
48+
public val remainingBufferSpace: DeviceState<Numeric<U>> = combineState(
49+
bufferCapacity, content
50+
) { capacity: Numeric<U>, content: T ->
51+
capacity - Numeric<U>(content.value)
52+
}
53+
54+
override val productionCapacity: DeviceState<T> = combineState(
55+
supplyRequest,
56+
content
57+
) { supply: T, content: T ->
58+
with(algebra) {
59+
supply + content
60+
}
61+
}
62+
63+
override val production: DeviceState<T> = combineState(
64+
productionCapacity,
65+
consumerRequest
66+
) { capacity, request ->
67+
with(algebra) {
68+
capacity.coerceValueIn(Numeric.zero<U>()..request)
69+
}
70+
}
71+
72+
override val consumationCapacity: DeviceState<Numeric<U>> = combineState(
73+
remainingBufferSpace,
74+
consumerRequest
75+
) { remaining: Numeric<U>, consumation: Numeric<U> ->
76+
remaining + consumation
77+
}
78+
79+
override val consumation: DeviceState<T> = combineState(
80+
supplyRequest,
81+
consumationCapacity
82+
) { supply: T, capacity ->
83+
with(algebra) {
84+
supply.coerceValueIn(Numeric.zero<U>()..capacity)
85+
}
86+
}
87+
88+
private val levelChange = onTimer(
89+
tick = timeStep,
90+
reads = listOf(production, consumation, remainingBufferSpace),
91+
writes = listOf(content)
92+
) { prev, next ->
93+
with(algebra) {
94+
delay(timeStep)
95+
96+
val delta = consumation.value - production.value
97+
98+
_content.value = (_content.value + delta * (timeStep / 1.seconds))
99+
.coerceValueIn(Numeric.zero<U>()..bufferCapacity.value)
100+
}
101+
}
102+
}
103+
104+
/**
105+
* Creates a [ContinuousBuffer] model that represents a buffer for storing and managing
106+
* quantities of a measurable unit in a simulation context. The buffer ensures that its
107+
* contents respect the given capacity constraints and dynamically manages supply requests.
108+
*
109+
* @param context The simulation context in which the buffer operates.
110+
* @param capacity The maximum capacity of the buffer, represented as a [Numeric] value.
111+
* @param supplyRequest A device state representing the amount requested to be supplied
112+
* to the buffer. Defaults to a [LateBindDeviceState] with a value of zero.
113+
* @return A [ContinuousBuffer] instance configured with the specified parameters.
114+
*/
115+
public fun <U : UnitsOfMeasurement> ContinuousBuffer(
116+
context: Context,
117+
capacity: Numeric<U>,
118+
supplyRequest: LateBindDeviceState<Numeric<U>> = LateBindDeviceState(Numeric(0))
119+
): ContinuousBuffer<U, Numeric<U>> = ContinuousBuffer(
120+
context,
121+
NumericAmountAlgebra<U>(),
122+
DeviceState(capacity),
123+
supplyRequest
124+
)

controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/models/flow/ContinuousConsumer.kt

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,61 @@ import space.kscience.controls.constructor.*
44
import space.kscience.controls.constructor.units.*
55
import space.kscience.dataforge.context.Context
66

7+
public interface ContinuousConsumerInterface<U: UnitsOfMeasurement, T : Amount<U>> {
8+
public val consumation: DeviceState<T>
9+
public val consumationCapacity: DeviceState<Numeric<U>>
10+
public val supplyRequest: LateBindDeviceState<T>
11+
12+
}
13+
14+
public fun <U : UnitsOfMeasurement, T : Amount<U>> ContinuousConsumerInterface<U, T>.connectProducer(
15+
producerCapacity: DeviceState<T>,
16+
) {
17+
supplyRequest.bind(producerCapacity)
18+
}
19+
720
/**
821
* Represents a model for a material flow consumer capable of consuming material flow based on its defined capacity
922
* and requested supply. This class calculates the actual material flow consumed and the efficiency of consumption.
1023
*
1124
* @param U The type of units of measurement for the material flow.
1225
* @param context The execution context used for state management and operations.
13-
* @param capacity The maximum capacity for material flow consumption of the consumer.
26+
* @param consumationCapacity The maximum capacity for material flow consumption of the consumer.
1427
* @param supplyRequest The state representing the requested material flow to be supplied.
1528
*
1629
* @property consumation A device state representing the actual material flow consumed,
1730
* calculated as the minimum of the requested supply and the consumer's capacity.
1831
* @property efficiency A device state representing the efficiency of the consumer, calculated
1932
* as the ratio of the actual consumption to the capacity.
2033
*/
21-
public class ContinuousConsumer<T : Amount<*>>(
34+
public class ContinuousConsumer<U: UnitsOfMeasurement, T : Amount<U>>(
2235
context: Context,
23-
public val algebra: AmountAlgebra<T>,
24-
public val capacity: DeviceState<T>,
25-
public val supplyRequest: LateBindDeviceState<T> = LateBindDeviceState(algebra.zero)
26-
) : ModelConstructor(context) {
36+
public val algebra: AmountAlgebra<U, T>,
37+
override val consumationCapacity: DeviceState<Numeric<U>>,
38+
override val supplyRequest: LateBindDeviceState<T> = LateBindDeviceState(algebra.zero)
39+
) : ModelConstructor(context), ContinuousConsumerInterface<U, T> {
2740

2841
init {
29-
registerState(capacity)
42+
registerState(consumationCapacity)
3043
registerState(supplyRequest)
3144
}
3245

33-
public val consumation: DeviceState<T> = combineState(
46+
override val consumation: DeviceState<T> = combineState(
3447
supplyRequest,
35-
capacity
48+
consumationCapacity
3649
) { request, capacity ->
3750
with(algebra) {
38-
minOf(request, capacity)
51+
request.coerceValueIn(Numeric.zero<U>()..capacity)
3952
}
4053
}
4154

4255
public val efficiency: DeviceState<Double> = combineState(
4356
consumation,
44-
capacity
57+
consumationCapacity
4558
) { consumation, capacity ->
4659
consumation.value / capacity.value
4760
}
4861

49-
public fun connectProducer(
50-
producerCapacity: DeviceState<T>,
51-
) {
52-
this.supplyRequest.bind(producerCapacity)
53-
}
54-
5562
public companion object
5663
}
5764

@@ -70,4 +77,4 @@ public fun <U : UnitsOfMeasurement> ContinuousConsumer(
7077
context: Context,
7178
capacity: DeviceState<Numeric<U>>,
7279
supplyRequest: LateBindDeviceState<Numeric<U>> = LateBindDeviceState(Numeric(0))
73-
): ContinuousConsumer<Numeric<U>> = ContinuousConsumer(context, NumericAmountAlgebra<U>(), capacity, supplyRequest)
80+
): ContinuousConsumer<U, Numeric<U>> = ContinuousConsumer(context, NumericAmountAlgebra<U>(), capacity, supplyRequest)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package space.kscience.controls.constructor.models.flow
2+
3+
import space.kscience.controls.constructor.DeviceState
4+
import space.kscience.controls.constructor.LateBindDeviceState
5+
import space.kscience.controls.constructor.ModelConstructor
6+
import space.kscience.controls.constructor.model
7+
import space.kscience.controls.constructor.units.Amount
8+
import space.kscience.controls.constructor.units.Numeric
9+
import space.kscience.controls.constructor.units.UnitsOfMeasurement
10+
import space.kscience.dataforge.context.Context
11+
12+
public abstract class ContinuousFlowModel(
13+
context: Context,
14+
vararg dependencies: DeviceState<*>
15+
) : ModelConstructor(context, *dependencies) {
16+
17+
public companion object {
18+
public fun <U : UnitsOfMeasurement, T : Amount<U>> connect(
19+
producer: ContinuousProducerInterface<U, T>,
20+
consumer: ContinuousConsumerInterface<U, T>,
21+
) {
22+
producer.connectConsumer(consumer.consumationCapacity)
23+
consumer.connectProducer(producer.productionCapacity)
24+
}
25+
}
26+
}
27+
28+
public fun <U : UnitsOfMeasurement> ContinuousFlowModel.producer(
29+
capacity: DeviceState<Numeric<U>>,
30+
supplyRequest: LateBindDeviceState<Numeric<U>> = LateBindDeviceState(Numeric(0))
31+
): ContinuousProducer<U, Numeric<U>> = model(ContinuousProducer(context, capacity, supplyRequest))
32+
33+
public fun <U : UnitsOfMeasurement> ContinuousFlowModel.consumer(
34+
capacity: DeviceState<Numeric<U>>,
35+
supplyRequest: LateBindDeviceState<Numeric<U>> = LateBindDeviceState(Numeric(0))
36+
): ContinuousConsumer<U, Numeric<U>> = model(ContinuousConsumer(context, capacity, supplyRequest))

0 commit comments

Comments
 (0)