WIP: Простая конфигурация устройств через спецификации #12
@ -23,6 +23,7 @@ kscience{
|
||||
commonTest{
|
||||
implementation(spclibs.logback.classic)
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,6 +74,18 @@ public interface StateContainer : ContextAware, CoroutineScope {
|
||||
}.launchIn(this@StateContainer).also {
|
||||
registerElement(ConnectionConstrucorElement(reads + this, writes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and register a derived state.
|
||||
*/
|
||||
public fun <T> derivedState(
|
||||
|
||||
dependencies: List<DeviceState<*>>,
|
||||
computeValue: () -> T,
|
||||
altavir
commented
Should be suspended Should be suspended
|
||||
): DeviceStateWithDependencies<T> {
|
||||
val state = DeviceState.derived(this, dependencies, computeValue)
|
||||
registerElement(StateConstructorElement(state))
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -210,7 +222,7 @@ public fun <T1, T2, R> StateContainer.combineTo(
|
||||
): Job {
|
||||
val descriptor = ConnectionConstrucorElement(setOf(sourceState1, sourceState2), setOf(targetState))
|
||||
registerElement(descriptor)
|
||||
return kotlinx.coroutines.flow.combine(sourceState1.valueFlow, sourceState2.valueFlow, transformation).onEach {
|
||||
return combine(sourceState1.valueFlow, sourceState2.valueFlow, transformation).onEach {
|
||||
targetState.value = it
|
||||
}.launchIn(this).apply {
|
||||
invokeOnCompletion {
|
||||
@ -231,7 +243,7 @@ public inline fun <reified T, R> StateContainer.combineTo(
|
||||
): Job {
|
||||
val descriptor = ConnectionConstrucorElement(sourceStates, setOf(targetState))
|
||||
registerElement(descriptor)
|
||||
return kotlinx.coroutines.flow.combine(sourceStates.map { it.valueFlow }, transformation).onEach {
|
||||
return combine(sourceStates.map { it.valueFlow }, transformation).onEach {
|
||||
targetState.value = it
|
||||
}.launchIn(this).apply {
|
||||
invokeOnCompletion {
|
||||
|
@ -2,10 +2,7 @@ package space.kscience.controls.constructor
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
import space.kscience.controls.constructor.units.NumericalValue
|
||||
import space.kscience.controls.constructor.units.UnitsOfMeasurement
|
||||
import kotlin.reflect.KProperty
|
||||
@ -44,18 +41,43 @@ public operator fun <T> MutableDeviceState<T>.setValue(thisRef: Any?, property:
|
||||
}
|
||||
|
||||
/**
|
||||
* Device state with a value that depends on other device states
|
||||
* DeviceState with dependencies on other DeviceStates.
|
||||
*/
|
||||
public interface DeviceStateWithDependencies<T> : DeviceState<T> {
|
||||
public val dependencies: Collection<DeviceState<*>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension function to create a DerivedDeviceState.
|
||||
*/
|
||||
public fun <T> DeviceState.Companion.derived(
|
||||
scope: CoroutineScope,
|
||||
dependencies: List<DeviceState<*>>,
|
||||
computeValue: () -> T
|
||||
altavir
commented
Should use suspended function Should use suspended function
|
||||
): DeviceStateWithDependencies<T> = DerivedDeviceState(scope, dependencies, computeValue)
|
||||
|
||||
public fun <T> DeviceState<T>.withDependencies(
|
||||
dependencies: Collection<DeviceState<*>>,
|
||||
): DeviceStateWithDependencies<T> = object : DeviceStateWithDependencies<T>, DeviceState<T> by this {
|
||||
override val dependencies: Collection<DeviceState<*>> = dependencies
|
||||
}
|
||||
|
||||
|
||||
public fun <T> DeviceState.Companion.fromFlow(
|
||||
altavir
commented
Should be documented Should be documented
|
||||
scope: CoroutineScope,
|
||||
flow: Flow<T>,
|
||||
initialValue: T
|
||||
): DeviceState<T> {
|
||||
val stateFlow = flow.stateIn(scope, SharingStarted.Eagerly, initialValue)
|
||||
return object : DeviceState<T> {
|
||||
override val value: T get() = stateFlow.value
|
||||
override val valueFlow: Flow<T> get() = stateFlow
|
||||
override fun toString(): String {
|
||||
return "DeviceState.fromFlow(scope=$scope, flow=$flow, initialValue=$initialValue)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new read-only [DeviceState] that mirrors receiver state by mapping the value with [mapper].
|
||||
*/
|
||||
@ -101,3 +123,27 @@ public fun <T1, T2, R> DeviceState.Companion.combine(
|
||||
|
||||
override fun toString(): String = "DeviceState.combine(state1=$state1, state2=$state2)"
|
||||
}
|
||||
|
||||
/**
|
||||
* A DeviceState that derives its value from other DeviceStates.
|
||||
*/
|
||||
public class DerivedDeviceState<T>(
|
||||
scope: CoroutineScope,
|
||||
override val dependencies: List<DeviceState<*>>,
|
||||
computeValue: () -> T
|
||||
) : DeviceStateWithDependencies<T> {
|
||||
private val _valueFlow: StateFlow<T>
|
||||
|
||||
init {
|
||||
altavir
commented
Don't assign values in init. Use fake constructor if needed Don't assign values in init. Use fake constructor if needed
|
||||
val flows = dependencies.map { it.valueFlow }
|
||||
_valueFlow = combine(flows) {
|
||||
computeValue()
|
||||
}.stateIn(scope, SharingStarted.Eagerly, computeValue())
|
||||
}
|
||||
|
||||
override val value: T get() = _valueFlow.value
|
||||
override val valueFlow: Flow<T> get() = _valueFlow
|
||||
override fun toString(): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ package space.kscience.controls.constructor.devices
|
||||
import space.kscience.controls.constructor.DeviceConstructor
|
||||
import space.kscience.controls.constructor.MutableDeviceState
|
||||
import space.kscience.controls.constructor.property
|
||||
import space.kscience.controls.constructor.units.NewtonsMeters
|
||||
import space.kscience.controls.constructor.units.NewtonMeters
|
||||
import space.kscience.controls.constructor.units.NumericalValue
|
||||
import space.kscience.controls.constructor.units.numerical
|
||||
import space.kscience.dataforge.context.Context
|
||||
@ -13,7 +13,7 @@ import space.kscience.dataforge.meta.MetaConverter
|
||||
|
||||
public class Drive(
|
||||
context: Context,
|
||||
force: MutableDeviceState<NumericalValue<NewtonsMeters>> = MutableDeviceState(NumericalValue(0)),
|
||||
force: MutableDeviceState<NumericalValue<NewtonMeters>> = MutableDeviceState(NumericalValue(0)),
|
||||
) : DeviceConstructor(context) {
|
||||
public val force: MutableDeviceState<NumericalValue<NewtonsMeters>> by property(MetaConverter.numerical(), force)
|
||||
public val force: MutableDeviceState<NumericalValue<NewtonMeters>> by property(MetaConverter.numerical(), force)
|
||||
}
|
@ -4,7 +4,7 @@ import space.kscience.controls.constructor.*
|
||||
import space.kscience.controls.constructor.models.PidParameters
|
||||
import space.kscience.controls.constructor.models.PidRegulator
|
||||
import space.kscience.controls.constructor.units.Meters
|
||||
import space.kscience.controls.constructor.units.NewtonsMeters
|
||||
import space.kscience.controls.constructor.units.NewtonMeters
|
||||
import space.kscience.controls.constructor.units.NumericalValue
|
||||
import space.kscience.controls.constructor.units.numerical
|
||||
import space.kscience.dataforge.context.Context
|
||||
@ -24,7 +24,7 @@ public class LinearDrive(
|
||||
public val position: DeviceState<NumericalValue<Meters>> by property(MetaConverter.numerical(), position)
|
||||
|
||||
public val drive: Drive by device(drive)
|
||||
public val pid: PidRegulator<Meters, NewtonsMeters> = model(
|
||||
public val pid: PidRegulator<Meters, NewtonMeters> = model(
|
||||
PidRegulator(
|
||||
context = context,
|
||||
position = position,
|
||||
|
@ -1,9 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core
|
||||
|
||||
/**
|
||||
* Класс для представления аннотации.
|
||||
*/
|
||||
public data class Annotation(
|
||||
val name: String,
|
||||
val properties: Map<String, Any>
|
||||
)
|
@ -1,196 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.equations.*
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.*
|
||||
import space.kscience.controls.constructor.dsl.core.variables.ArrayVariable
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Класс для хранения и обработки системы уравнений.
|
||||
*/
|
||||
public class EquationSystem {
|
||||
public val equations: MutableList<EquationBase> = mutableListOf()
|
||||
public val variables: MutableSet<String> = mutableSetOf()
|
||||
public val derivativeVariables: MutableSet<String> = mutableSetOf()
|
||||
public val algebraicEquations: MutableList<Equation> = mutableListOf()
|
||||
public val initialEquations: MutableList<EquationBase> = mutableListOf()
|
||||
public val conditionalEquations: MutableList<ConditionalEquation> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Метод для добавления списка уравнений.
|
||||
*/
|
||||
public fun addEquations(eqs: List<EquationBase>) {
|
||||
for (eq in eqs) {
|
||||
addEquation(eq)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для добавления одного уравнения.
|
||||
*/
|
||||
public fun addEquation(eq: EquationBase) {
|
||||
equations.add(eq)
|
||||
collectVariables(eq)
|
||||
}
|
||||
|
||||
private fun collectVariables(eq: EquationBase) {
|
||||
when (eq) {
|
||||
is Equation -> {
|
||||
collectVariablesFromExpression(eq.leftExpression)
|
||||
collectVariablesFromExpression(eq.rightExpression)
|
||||
|
||||
// Classify the equation
|
||||
if (isDifferentialEquation(eq)) {
|
||||
// Handled in toODESystem()
|
||||
} else {
|
||||
algebraicEquations.add(eq)
|
||||
}
|
||||
}
|
||||
is ConditionalEquation -> {
|
||||
collectVariablesFromExpression(eq.condition)
|
||||
collectVariables(eq.trueEquation)
|
||||
eq.falseEquation?.let { collectVariables(it) }
|
||||
conditionalEquations.add(eq)
|
||||
}
|
||||
is InitialEquation -> {
|
||||
collectVariablesFromExpression(eq.leftExpression)
|
||||
collectVariablesFromExpression(eq.rightExpression)
|
||||
initialEquations.add(eq)
|
||||
}
|
||||
else -> {
|
||||
throw UnsupportedOperationException("Unknown equation type: ${eq::class}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectVariablesFromExpression(expr: Expression) {
|
||||
when (expr) {
|
||||
is VariableExpression -> variables.add(expr.variable.name)
|
||||
is ConstantExpression -> {
|
||||
// Constants do not contain variables
|
||||
}
|
||||
is DerivativeExpression -> {
|
||||
collectVariablesFromExpression(expr.variable)
|
||||
derivativeVariables.add(expr.variable.variable.name)
|
||||
}
|
||||
is PartialDerivativeExpression -> {
|
||||
collectVariablesFromExpression(expr.variable)
|
||||
collectVariablesFromExpression(expr.respectTo)
|
||||
// For partial derivatives, variables may need special handling
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
collectVariablesFromExpression(expr.left)
|
||||
collectVariablesFromExpression(expr.right)
|
||||
}
|
||||
is FunctionCallExpression -> {
|
||||
expr.arguments.forEach { collectVariablesFromExpression(it) }
|
||||
}
|
||||
is ArrayVariableExpression -> {
|
||||
collectVariablesFromArrayVariable(expr.left)
|
||||
collectVariablesFromArrayVariable(expr.right)
|
||||
}
|
||||
is ArrayScalarExpression -> {
|
||||
collectVariablesFromArrayVariable(expr.array)
|
||||
collectVariablesFromExpression(expr.scalar)
|
||||
}
|
||||
else -> {
|
||||
throw UnsupportedOperationException("Unknown expression type: ${expr::class}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectVariablesFromArrayVariable(arrayVar: ArrayVariable) {
|
||||
variables.add(arrayVar.name)
|
||||
// Additional processing if necessary
|
||||
}
|
||||
|
||||
private fun isDifferentialEquation(eq: Equation): Boolean {
|
||||
val leftIsDerivative = containsDerivative(eq.leftExpression)
|
||||
val rightIsDerivative = containsDerivative(eq.rightExpression)
|
||||
return leftIsDerivative || rightIsDerivative
|
||||
}
|
||||
|
||||
private fun containsDerivative(expr: Expression): Boolean {
|
||||
return when (expr) {
|
||||
is DerivativeExpression -> true
|
||||
is BinaryExpression -> containsDerivative(expr.left) || containsDerivative(expr.right)
|
||||
is FunctionCallExpression -> expr.arguments.any { containsDerivative(it) }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Подготовка системы ОДУ для численного решения.
|
||||
*/
|
||||
public fun toODESystem(): ODESystem {
|
||||
val odeEquations = mutableListOf<ODEEquation>()
|
||||
|
||||
// Process equations to separate ODEs and algebraic equations
|
||||
for (eqBase in equations) {
|
||||
when (eqBase) {
|
||||
is Equation -> {
|
||||
val eq = eqBase
|
||||
if (isDifferentialEquation(eq)) {
|
||||
// Handle differential equations
|
||||
processDifferentialEquation(eq, odeEquations)
|
||||
} else {
|
||||
// Handle algebraic equations
|
||||
algebraicEquations.add(eq)
|
||||
}
|
||||
}
|
||||
is ConditionalEquation -> {
|
||||
// Handle conditional equations
|
||||
// For ODE system, we might need to process them differently
|
||||
conditionalEquations.add(eqBase)
|
||||
}
|
||||
is InitialEquation -> {
|
||||
// Initial equations are used for setting initial conditions
|
||||
initialEquations.add(eqBase)
|
||||
}
|
||||
else -> {
|
||||
throw UnsupportedOperationException("Unknown equation type: ${eqBase::class}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ODESystem(odeEquations, algebraicEquations, initialEquations, conditionalEquations)
|
||||
}
|
||||
|
||||
private fun processDifferentialEquation(eq: Equation, odeEquations: MutableList<ODEEquation>) {
|
||||
// Identify which side has the derivative
|
||||
val derivativeExpr = findDerivativeExpression(eq.leftExpression) ?: findDerivativeExpression(eq.rightExpression)
|
||||
if (derivativeExpr != null) {
|
||||
val variableName = derivativeExpr.variable.variable.name
|
||||
if (derivativeExpr.order != 1) {
|
||||
throw UnsupportedOperationException("Only first-order derivatives are supported in this solver.")
|
||||
}
|
||||
|
||||
val rhs = if (eq.leftExpression is DerivativeExpression) eq.rightExpression else eq.leftExpression
|
||||
odeEquations.add(ODEEquation(variableName, rhs))
|
||||
derivativeVariables.add(variableName)
|
||||
} else {
|
||||
throw UnsupportedOperationException("Equation is marked as differential but no derivative found.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun findDerivativeExpression(expr: Expression): DerivativeExpression? {
|
||||
return when (expr) {
|
||||
is DerivativeExpression -> expr
|
||||
is BinaryExpression -> findDerivativeExpression(expr.left) ?: findDerivativeExpression(expr.right)
|
||||
is FunctionCallExpression -> expr.arguments.mapNotNull { findDerivativeExpression(it) }.firstOrNull()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public data class ODEEquation(
|
||||
val variableName: String,
|
||||
val rhs: Expression
|
||||
)
|
||||
|
||||
public class ODESystem(
|
||||
public val odeEquations: List<ODEEquation>,
|
||||
public val algebraicEquations: List<Equation>,
|
||||
public val initialEquations: List<EquationBase>,
|
||||
public val conditionalEquations: List<ConditionalEquation>
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Класс для представления записи (record).
|
||||
*/
|
||||
public class Record(public val name: String) {
|
||||
public val fields: MutableMap<String, Variable> = mutableMapOf()
|
||||
|
||||
|
||||
public fun field(name: String, unit: PhysicalUnit) {
|
||||
if (fields.containsKey(name)) {
|
||||
throw IllegalArgumentException("Field with name $name already exists in record $name.")
|
||||
}
|
||||
fields[name] = Variable(name, unit)
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.algorithms
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Класс для построения алгоритмов.
|
||||
*/
|
||||
public class AlgorithmBuilder {
|
||||
public val statements: MutableList<Statement> = mutableListOf()
|
||||
|
||||
public fun assign(variable: Variable, expression: Expression) {
|
||||
statements.add(AssignmentStatement(variable, expression))
|
||||
}
|
||||
|
||||
public fun ifStatement(condition: Expression, init: AlgorithmBuilder.() -> Unit) {
|
||||
val trueBuilder = AlgorithmBuilder()
|
||||
trueBuilder.init()
|
||||
statements.add(IfStatement(condition, trueBuilder.build(), null))
|
||||
}
|
||||
|
||||
public fun forStatement(variable: Variable, range: IntRange, init: AlgorithmBuilder.() -> Unit) {
|
||||
val bodyBuilder = AlgorithmBuilder()
|
||||
bodyBuilder.init()
|
||||
statements.add(ForStatement(variable, range, bodyBuilder.build()))
|
||||
}
|
||||
|
||||
public fun whileStatement(condition: Expression, init: AlgorithmBuilder.() -> Unit) {
|
||||
val bodyBuilder = AlgorithmBuilder()
|
||||
bodyBuilder.init()
|
||||
statements.add(WhileStatement(condition, bodyBuilder.build()))
|
||||
}
|
||||
|
||||
public fun build(): Algorithm {
|
||||
return Algorithm(statements.toList())
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.algorithms
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
import space.kscience.controls.constructor.dsl.core.simulation.SimulationContext
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
|
||||
/**
|
||||
* Абстрактный класс для операторов в алгоритмической секции.
|
||||
*/
|
||||
public sealed class Statement {
|
||||
public abstract suspend fun execute(context: SimulationContext)
|
||||
}
|
||||
|
||||
public data class AssignmentStatement(
|
||||
val variable: Variable,
|
||||
val expression: Expression
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: SimulationContext) {
|
||||
val value = expression.evaluate(context.getVariableValues())
|
||||
context.updateVariable(variable.name, value)
|
||||
}
|
||||
}
|
||||
|
||||
public data class IfStatement(
|
||||
val condition: Expression,
|
||||
val trueBranch: Algorithm,
|
||||
val falseBranch: Algorithm? = null
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: SimulationContext) {
|
||||
val conditionValue = condition.evaluate(context.getVariableValues())
|
||||
if (conditionValue != 0.0) {
|
||||
trueBranch.execute(context)
|
||||
} else {
|
||||
falseBranch?.execute(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public data class ForStatement(
|
||||
val variable: Variable,
|
||||
val range: IntRange,
|
||||
val body: Algorithm
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: SimulationContext) {
|
||||
for (i in range) {
|
||||
context.updateVariable(variable.name, i.toDouble())
|
||||
body.execute(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public data class WhileStatement(
|
||||
val condition: Expression,
|
||||
val body: Algorithm
|
||||
) : Statement() {
|
||||
override suspend fun execute(context: SimulationContext) {
|
||||
while (condition.evaluate(context.getVariableValues()) != 0.0) {
|
||||
body.execute(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Algorithm(private val statements: List<Statement>) {
|
||||
/**
|
||||
* Добавляет оператор в алгоритм.
|
||||
*/
|
||||
public fun addStatement(statement: Statement) {
|
||||
(statements as MutableList).add(statement)
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет алгоритм в заданном контексте симуляции.
|
||||
*/
|
||||
public suspend fun execute(context: SimulationContext) {
|
||||
for (statement in statements) {
|
||||
statement.execute(context)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.components
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.Annotation
|
||||
import space.kscience.controls.constructor.dsl.core.equations.Equation
|
||||
import space.kscience.controls.constructor.dsl.core.variables.ParameterVariable
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Базовый класс для всех компонентов модели.
|
||||
*/
|
||||
public abstract class Component<V : Variable, P : ParameterVariable, C : Connector>(public val name: String) {
|
||||
public val variables: MutableMap<String, V> = mutableMapOf()
|
||||
public val parameters: MutableMap<String, P> = mutableMapOf()
|
||||
public val equations: MutableList<Equation> = mutableListOf()
|
||||
public val connectors: MutableMap<String, C> = mutableMapOf()
|
||||
public val annotations: MutableList<Annotation> = mutableListOf()
|
||||
public var isReplaceable: Boolean = false
|
||||
|
||||
/**
|
||||
* Метод для инициализации переменных.
|
||||
*/
|
||||
protected abstract fun initializeVariables()
|
||||
|
||||
/**
|
||||
* Метод для определения уравнений компонента.
|
||||
*/
|
||||
protected abstract fun defineEquations()
|
||||
|
||||
/**
|
||||
* Метод для добавления переменной.
|
||||
*/
|
||||
public fun addVariable(variable: V) {
|
||||
if (variables.containsKey(variable.name)) {
|
||||
throw IllegalArgumentException("Variable with name ${variable.name} already exists in component $name.")
|
||||
}
|
||||
variables[variable.name] = variable
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для добавления параметра.
|
||||
*/
|
||||
public fun addParameter(parameter: P) {
|
||||
if (parameters.containsKey(parameter.name)) {
|
||||
throw IllegalArgumentException("Parameter with name ${parameter.name} already exists in component $name.")
|
||||
}
|
||||
parameters[parameter.name] = parameter
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для добавления уравнения.
|
||||
*/
|
||||
public fun addEquation(equation: Equation) {
|
||||
equations.add(equation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для добавления коннектора.
|
||||
*/
|
||||
public fun addConnector(name: String, connector: C) {
|
||||
if (connectors.containsKey(name)) {
|
||||
throw IllegalArgumentException("Connector with name $name already exists in component $name.")
|
||||
}
|
||||
connectors[name] = connector
|
||||
}
|
||||
|
||||
public fun annotate(annotation: Annotation) {
|
||||
annotations.add(annotation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для модификации параметров компонента.
|
||||
*/
|
||||
public fun modify(modifier: Component<V, P, C>.() -> Unit): Component<V, P, C> {
|
||||
this.apply(modifier)
|
||||
return this
|
||||
}
|
||||
|
||||
public fun markAsReplaceable(isReplaceable: Boolean) {
|
||||
this.isReplaceable = isReplaceable
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.components
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.equations.Equation
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.VariableExpression
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Класс для представления соединения между двумя коннекторами.
|
||||
*/
|
||||
public class Connection<C : Connector>(
|
||||
public val connector1: C,
|
||||
public val connector2: C
|
||||
) {
|
||||
init {
|
||||
if (connector1.unit != connector2.unit) {
|
||||
throw IllegalArgumentException("Cannot connect connectors with different units: ${connector1.unit} and ${connector2.unit}.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация уравнений для соединения.
|
||||
*/
|
||||
public fun generateEquations(): List<Equation> {
|
||||
val equations = mutableListOf<Equation>()
|
||||
for ((varName, var1) in connector1.variables) {
|
||||
val var2 = connector2.variables[varName]
|
||||
?: throw IllegalArgumentException("Variable $varName not found in connector ${connector2.name}.")
|
||||
|
||||
equations.add(
|
||||
Equation(
|
||||
VariableExpression(var1),
|
||||
VariableExpression(var2)
|
||||
)
|
||||
)
|
||||
}
|
||||
return equations
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.components
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Интерфейс для коннектора, позволяющего соединять компоненты.
|
||||
*/
|
||||
public interface Connector {
|
||||
public val name: String
|
||||
public val variables: Map<String, Variable>
|
||||
public val unit: PhysicalUnit
|
||||
}
|
||||
|
||||
/**
|
||||
* Базовая реализация коннектора.
|
||||
*/
|
||||
public class BasicConnector(
|
||||
override val name: String,
|
||||
override val unit: PhysicalUnit,
|
||||
override val variables: Map<String, Variable>
|
||||
) : Connector
|
@ -1,163 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.components
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.*
|
||||
import space.kscience.controls.constructor.dsl.core.algorithms.*
|
||||
import space.kscience.controls.constructor.dsl.core.equations.Equation
|
||||
import space.kscience.controls.constructor.dsl.core.equations.ConditionalEquation
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.VariableExpression
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.variables.ParameterVariable
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
import space.kscience.controls.constructor.dsl.core.functions.Function
|
||||
import space.kscience.controls.constructor.dsl.core.functions.FunctionBuilder
|
||||
|
||||
/**
|
||||
* Класс для представления модели, содержащей компоненты и соединения.
|
||||
*/
|
||||
public class Model(public val name: String, init: Model.() -> Unit) {
|
||||
public val components: MutableMap<String, Component<*, *, *>> = mutableMapOf<String, Component<*, *, *>>()
|
||||
public val connections: MutableList<Connection<*>> = mutableListOf<Connection<*>>()
|
||||
public val variables: MutableMap<String, Variable> = mutableMapOf<String, Variable>()
|
||||
public val parameters: MutableMap<String, ParameterVariable> = mutableMapOf<String, ParameterVariable>()
|
||||
public val equations: MutableList<Equation> = mutableListOf<Equation>()
|
||||
public val initialValues: MutableMap<String, Double> = mutableMapOf<String, Double>()
|
||||
public val initialEquations: MutableList<Equation> = mutableListOf<Equation>()
|
||||
public val conditionalEquations: MutableList<ConditionalEquation> = mutableListOf<ConditionalEquation>()
|
||||
public val algorithms: MutableList<Algorithm> = mutableListOf<Algorithm>()
|
||||
public val functions: MutableMap<String, Function> = mutableMapOf<String, Function>()
|
||||
|
||||
init {
|
||||
this.init()
|
||||
}
|
||||
|
||||
/**
|
||||
* Определение переменной.
|
||||
*/
|
||||
public fun variable(name: String, unit: PhysicalUnit): VariableExpression {
|
||||
val variable = Variable(name, unit)
|
||||
variables[name] = variable
|
||||
return VariableExpression(variable)
|
||||
}
|
||||
|
||||
/**
|
||||
* Определение параметра.
|
||||
*/
|
||||
public fun parameter(name: String, unit: PhysicalUnit, value: Double): ParameterVariable {
|
||||
val parameter = ParameterVariable(name, unit, value)
|
||||
parameters[name] = parameter
|
||||
return parameter
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление уравнения.
|
||||
*/
|
||||
public fun equation(equationBuilder: () -> Equation) {
|
||||
val equation = equationBuilder()
|
||||
equations.add(equation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка начальных значений.
|
||||
*/
|
||||
public fun initialValues(init: MutableMap<String, Double>.() -> Unit) {
|
||||
initialValues.apply(init)
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление инициализационного уравнения.
|
||||
*/
|
||||
public fun initialEquation(equationBuilder: () -> Equation) {
|
||||
val equation = equationBuilder()
|
||||
initialEquations.add(equation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для добавления компонента.
|
||||
*/
|
||||
public fun addComponent(component: Component<*, *, *>) {
|
||||
if (components.containsKey(component.name)) {
|
||||
throw IllegalArgumentException("Component with name ${component.name} already exists in model $name.")
|
||||
}
|
||||
components[component.name] = component
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для добавления соединения.
|
||||
*/
|
||||
public fun addConnection(connection: Connection<*>) {
|
||||
connections.add(connection)
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для генерации системы уравнений модели.
|
||||
*/
|
||||
public fun generateEquationSystem(): EquationSystem {
|
||||
val equationSystem = EquationSystem()
|
||||
|
||||
// Добавление уравнений модели
|
||||
equationSystem.addEquations(equations)
|
||||
|
||||
// Добавление уравнений компонентов
|
||||
for (component in components.values) {
|
||||
equationSystem.addEquations(component.equations)
|
||||
}
|
||||
|
||||
// Добавление уравнений соединений
|
||||
for (connection in connections) {
|
||||
equationSystem.addEquations(connection.generateEquations())
|
||||
}
|
||||
|
||||
// Добавление условных уравнений
|
||||
for (condEq in conditionalEquations) {
|
||||
equationSystem.addEquation(condEq)
|
||||
}
|
||||
|
||||
// Добавление инициализационных уравнений
|
||||
equationSystem.addEquations(initialEquations)
|
||||
|
||||
return equationSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для замены компонента.
|
||||
*/
|
||||
public fun replaceComponent(name: String, newComponent: Component<*, *, *>) {
|
||||
val oldComponent = components[name]
|
||||
if (oldComponent != null && oldComponent.isReplaceable) {
|
||||
components[name] = newComponent
|
||||
} else {
|
||||
throw IllegalArgumentException("Component $name is not replaceable or does not exist.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление условного уравнения.
|
||||
*/
|
||||
public fun conditionalEquation(condition: Expression, trueEquation: Equation, falseEquation: Equation? = null) {
|
||||
val condEq = ConditionalEquation(condition, trueEquation, falseEquation)
|
||||
conditionalEquations.add(condEq)
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление алгоритма.
|
||||
*/
|
||||
public fun algorithm(init: AlgorithmBuilder.() -> Unit) {
|
||||
val builder = AlgorithmBuilder()
|
||||
builder.init()
|
||||
algorithms.add(builder.build())
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление пользовательской функции.
|
||||
*/
|
||||
public fun function(name: String, init: FunctionBuilder.() -> Unit) {
|
||||
if (functions.containsKey(name)) {
|
||||
throw IllegalArgumentException("Function with name $name already exists in model $name.")
|
||||
}
|
||||
val builder = FunctionBuilder(name)
|
||||
builder.init()
|
||||
val function = builder.build()
|
||||
functions[name] = function
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.components
|
||||
|
||||
/**
|
||||
* Класс для представления пакета моделей.
|
||||
*/
|
||||
public class Package(public val name: String) {
|
||||
public val models: MutableMap<String, Model> = mutableMapOf()
|
||||
public val subPackages: MutableMap<String, Package> = mutableMapOf()
|
||||
|
||||
public fun addModel(model: Model) {
|
||||
models[model.name] = model
|
||||
}
|
||||
|
||||
public fun addPackage(pkg: Package) {
|
||||
subPackages[pkg.name] = pkg
|
||||
}
|
||||
|
||||
public fun getModel(name: String): Model? {
|
||||
return models[name] ?: subPackages.values.mapNotNull { it.getModel(name) }.firstOrNull()
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.equations
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
|
||||
/**
|
||||
* Класс для условного уравнения.
|
||||
*/
|
||||
public class ConditionalEquation(
|
||||
public val condition: Expression,
|
||||
public val trueEquation: Equation,
|
||||
public val falseEquation: Equation? = null
|
||||
) : EquationBase()
|
@ -1,24 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.equations
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
|
||||
/**
|
||||
* Класс для представления уравнения.
|
||||
*/
|
||||
public class Equation(
|
||||
public val leftExpression: Expression,
|
||||
public val rightExpression: Expression
|
||||
) : EquationBase() {
|
||||
init {
|
||||
// Проверка совместимости единиц измерения
|
||||
if (!leftExpression.unit.dimension.isCompatibleWith(rightExpression.unit.dimension)) {
|
||||
throw IllegalArgumentException("Units of left and right expressions in the equation are not compatible.")
|
||||
}
|
||||
}
|
||||
|
||||
public fun evaluate(values: Map<String, Double>): Double {
|
||||
val leftValue = leftExpression.evaluate(values)
|
||||
val rightValue = rightExpression.evaluate(values)
|
||||
return leftValue - rightValue
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.equations
|
||||
|
||||
/**
|
||||
* Абстрактный базовый класс для уравнений.
|
||||
*/
|
||||
public sealed class EquationBase
|
@ -1,11 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.equations
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
|
||||
/**
|
||||
* Class representing an initial equation for setting initial conditions.
|
||||
*/
|
||||
public class InitialEquation(
|
||||
public val leftExpression: Expression,
|
||||
public val rightExpression: Expression
|
||||
) : EquationBase()
|
@ -1,18 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.events
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
import space.kscience.controls.constructor.dsl.core.simulation.SimulationContext
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Действие присваивания нового значения переменной.
|
||||
*/
|
||||
public data class AssignAction(
|
||||
val variable: Variable,
|
||||
val newValue: Expression
|
||||
) : EventAction() {
|
||||
override suspend fun execute(context: SimulationContext) {
|
||||
val value = newValue.evaluate(context.getVariableValues())
|
||||
context.updateVariable(variable.name, value)
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.events
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.simulation.SimulationContext
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
|
||||
/**
|
||||
* Класс для представления события.
|
||||
*/
|
||||
public class Event(
|
||||
public val condition: Expression,
|
||||
public val actions: List<EventAction>
|
||||
) {
|
||||
/**
|
||||
* Метод для проверки, произошло ли событие.
|
||||
*/
|
||||
public suspend fun checkEvent(context: SimulationContext): Boolean {
|
||||
val values = context.getVariableValues()
|
||||
val conditionValue = condition.evaluate(values)
|
||||
return conditionValue > 0 // Событие происходит, когда условие положительно
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для выполнения действий при событии.
|
||||
*/
|
||||
public suspend fun executeActions(context: SimulationContext) {
|
||||
for (action in actions) {
|
||||
action.execute(context)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.events
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
import space.kscience.controls.constructor.dsl.core.simulation.SimulationContext
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Абстрактный класс для действий при событии.
|
||||
*/
|
||||
public sealed class EventAction {
|
||||
public abstract suspend fun execute(context: SimulationContext)
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.events
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
import space.kscience.controls.constructor.dsl.core.simulation.SimulationContext
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Действие реинициализации переменной.
|
||||
*/
|
||||
public data class ReinitAction(
|
||||
val variable: Variable,
|
||||
val newValue: Expression
|
||||
) : EventAction() {
|
||||
override suspend fun execute(context: SimulationContext) {
|
||||
val value = newValue.evaluate(context.getVariableValues())
|
||||
context.updateVariable(variable.name, value)
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expression
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.variables.ArrayVariable
|
||||
import space.kscience.controls.constructor.dsl.core.equations.Equation
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.ArrayBinaryExpression
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.ArrayBinaryOperator
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.ArrayScalarExpression
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.ArrayScalarOperator
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.BinaryExpression
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.BinaryOperator
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.ConstantExpression
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.FunctionCallExpression
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
import space.kscience.controls.constructor.dsl.core.functions.Function
|
||||
|
||||
// Операторные функции для скалярных выражений
|
||||
|
||||
public operator fun Expression.plus(other: Expression): Expression {
|
||||
return BinaryExpression(this, BinaryOperator.ADD, other)
|
||||
}
|
||||
|
||||
public operator fun Expression.minus(other: Expression): Expression {
|
||||
return BinaryExpression(this, BinaryOperator.SUBTRACT, other)
|
||||
}
|
||||
|
||||
public operator fun Expression.times(other: Expression): Expression {
|
||||
return BinaryExpression(this, BinaryOperator.MULTIPLY, other)
|
||||
}
|
||||
|
||||
public operator fun Expression.div(other: Expression): Expression {
|
||||
return BinaryExpression(this, BinaryOperator.DIVIDE, other)
|
||||
}
|
||||
|
||||
public infix fun Expression.pow(exponent: Expression): Expression {
|
||||
return FunctionCallExpression("pow", listOf(this, exponent))
|
||||
}
|
||||
|
||||
public infix fun Expression.pow(exponent: Double): Expression {
|
||||
return FunctionCallExpression("pow", listOf(this, ConstantExpression(exponent, Units.dimensionless)))
|
||||
}
|
||||
|
||||
// Сравнительные операторы
|
||||
|
||||
public infix fun Expression.eq(other: Expression): Equation {
|
||||
return Equation(this, other)
|
||||
}
|
||||
|
||||
public infix fun Expression.gt(other: Expression): Expression {
|
||||
return BinaryExpression(this, BinaryOperator.GREATER_THAN, other)
|
||||
}
|
||||
|
||||
public infix fun Expression.lt(other: Expression): Expression {
|
||||
return BinaryExpression(this, BinaryOperator.LESS_THAN, other)
|
||||
}
|
||||
|
||||
public infix fun Expression.ge(other: Expression): Expression {
|
||||
return BinaryExpression(this, BinaryOperator.GREATER_OR_EQUAL, other)
|
||||
}
|
||||
|
||||
public infix fun Expression.le(other: Expression): Expression {
|
||||
return BinaryExpression(this, BinaryOperator.LESS_OR_EQUAL, other)
|
||||
}
|
||||
|
||||
// Операторные функции для массивов
|
||||
|
||||
public operator fun ArrayVariable.plus(other: ArrayVariable): Expression {
|
||||
return ArrayBinaryExpression(this, ArrayBinaryOperator.ADD, other)
|
||||
}
|
||||
|
||||
public operator fun ArrayVariable.minus(other: ArrayVariable): Expression {
|
||||
return ArrayBinaryExpression(this, ArrayBinaryOperator.SUBTRACT, other)
|
||||
}
|
||||
|
||||
public operator fun ArrayVariable.times(scalar: Expression): Expression {
|
||||
return ArrayScalarExpression(this, ArrayScalarOperator.MULTIPLY, scalar)
|
||||
}
|
||||
|
||||
public operator fun ArrayVariable.div(scalar: Expression): Expression {
|
||||
return ArrayScalarExpression(this, ArrayScalarOperator.DIVIDE, scalar)
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция для вызова пользовательской функции в выражении.
|
||||
*/
|
||||
public fun callFunction(function: Function, vararg args: Expression): FunctionCallExpression {
|
||||
require(args.size == function.inputs.size) {
|
||||
"Number of arguments does not match number of function inputs."
|
||||
}
|
||||
return FunctionCallExpression(function.name, args.toList())
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.variables.ArrayVariable
|
||||
|
||||
/**
|
||||
* Класс для представления бинарной операции между двумя массивами.
|
||||
*/
|
||||
public class ArrayBinaryExpression(
|
||||
public val left: ArrayVariable,
|
||||
public val operator: ArrayBinaryOperator,
|
||||
public val right: ArrayVariable
|
||||
) : Expression() {
|
||||
|
||||
override val unit: PhysicalUnit = when (operator) {
|
||||
ArrayBinaryOperator.ADD, ArrayBinaryOperator.SUBTRACT -> {
|
||||
if (left.unit != right.unit) {
|
||||
throw IllegalArgumentException("Units must be the same for array addition or subtraction.")
|
||||
}
|
||||
left.unit
|
||||
}
|
||||
ArrayBinaryOperator.MULTIPLY -> left.unit * right.unit
|
||||
ArrayBinaryOperator.DIVIDE -> left.unit / right.unit
|
||||
}
|
||||
|
||||
override fun evaluate(values: Map<String, Double>): Double {
|
||||
throw UnsupportedOperationException("Cannot evaluate array expression to a scalar value.")
|
||||
}
|
||||
|
||||
override fun checkDimensions() {
|
||||
TODO()
|
||||
}
|
||||
|
||||
override fun derivative(variable: VariableExpression): Expression {
|
||||
throw UnsupportedOperationException("Derivative of array expression is not implemented.")
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
/**
|
||||
* Перечисление для бинарных операций над массивами.
|
||||
*/
|
||||
public enum class ArrayBinaryOperator {
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
MULTIPLY,
|
||||
DIVIDE
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
public sealed class ArrayExpression : Expression()
|
@ -1,34 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.variables.ArrayVariable
|
||||
|
||||
/**
|
||||
* Представляет операцию между массивом и скаляром.
|
||||
*/
|
||||
public class ArrayScalarExpression(
|
||||
public val array: ArrayVariable,
|
||||
public val operator: ArrayScalarOperator,
|
||||
public val scalar: Expression
|
||||
) : Expression() {
|
||||
|
||||
override val unit: PhysicalUnit = when (operator) {
|
||||
ArrayScalarOperator.MULTIPLY -> array.unit * scalar.unit
|
||||
ArrayScalarOperator.DIVIDE -> array.unit / scalar.unit
|
||||
}
|
||||
|
||||
override fun evaluate(values: Map<String, Double>): Double {
|
||||
// Невозможно вычислить массивное выражение до скалярного значения
|
||||
throw UnsupportedOperationException("Cannot evaluate array expression to a scalar value.")
|
||||
}
|
||||
|
||||
override fun checkDimensions() {
|
||||
// array.checkDimensions()
|
||||
scalar.checkDimensions()
|
||||
// Единицы измерения уже проверены в свойстве `unit`
|
||||
}
|
||||
|
||||
override fun derivative(variable: VariableExpression): Expression {
|
||||
throw UnsupportedOperationException("Derivative of array expression is not implemented.")
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
/**
|
||||
* Перечисление операторов между массивом и скаляром.
|
||||
*/
|
||||
public enum class ArrayScalarOperator {
|
||||
MULTIPLY,
|
||||
DIVIDE
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.Expression
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.variables.ArrayVariable
|
||||
|
||||
/**
|
||||
* Представляет операцию между двумя массивами.
|
||||
*/
|
||||
public class ArrayVariableExpression(
|
||||
public val left: ArrayVariable,
|
||||
public val operator: ArrayBinaryOperator,
|
||||
public val right: ArrayVariable
|
||||
) : Expression() {
|
||||
|
||||
override val unit: PhysicalUnit = when (operator) {
|
||||
ArrayBinaryOperator.ADD, ArrayBinaryOperator.SUBTRACT -> {
|
||||
if (left.unit != right.unit) {
|
||||
throw IllegalArgumentException("Units must be the same for array addition or subtraction.")
|
||||
}
|
||||
left.unit
|
||||
}
|
||||
ArrayBinaryOperator.MULTIPLY -> left.unit * right.unit
|
||||
ArrayBinaryOperator.DIVIDE -> left.unit / right.unit
|
||||
}
|
||||
|
||||
override fun evaluate(values: Map<String, Double>): Double {
|
||||
// Невозможно вычислить массивное выражение до скалярного значения
|
||||
throw UnsupportedOperationException("Cannot evaluate array expression to a scalar value.")
|
||||
}
|
||||
|
||||
override fun checkDimensions() {
|
||||
// left.checkDimensions()
|
||||
// right.checkDimensions()
|
||||
// Единицы измерения уже проверены в свойстве `unit`
|
||||
}
|
||||
|
||||
override fun derivative(variable: VariableExpression): Expression {
|
||||
// Производная массива не реализована
|
||||
throw UnsupportedOperationException("Derivative of array expression is not implemented.")
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
import kotlin.math.*
|
||||
|
||||
/**
|
||||
* Перечисление типов бинарных операций.
|
||||
*/
|
||||
public enum class BinaryOperator {
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
MULTIPLY,
|
||||
DIVIDE,
|
||||
POWER,
|
||||
GREATER_THAN,
|
||||
LESS_THAN,
|
||||
GREATER_OR_EQUAL,
|
||||
LESS_OR_EQUAL
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс для представления бинарной операции.
|
||||
*/
|
||||
public class BinaryExpression(
|
||||
public val left: Expression,
|
||||
public val operator: BinaryOperator,
|
||||
public val right: Expression
|
||||
) : Expression() {
|
||||
|
||||
override val unit: PhysicalUnit = when (operator) {
|
||||
BinaryOperator.ADD, BinaryOperator.SUBTRACT -> {
|
||||
if (left.unit != right.unit) {
|
||||
throw IllegalArgumentException("Units of left and right expressions must be the same for addition or subtraction.")
|
||||
}
|
||||
left.unit
|
||||
}
|
||||
BinaryOperator.MULTIPLY -> left.unit * right.unit
|
||||
BinaryOperator.DIVIDE -> left.unit / right.unit
|
||||
BinaryOperator.POWER -> {
|
||||
if (right is ConstantExpression) {
|
||||
left.unit.pow(right.value)
|
||||
} else {
|
||||
throw IllegalArgumentException("Exponent must be a constant expression.")
|
||||
}
|
||||
}
|
||||
BinaryOperator.GREATER_THAN, BinaryOperator.LESS_THAN,
|
||||
BinaryOperator.GREATER_OR_EQUAL, BinaryOperator.LESS_OR_EQUAL -> {
|
||||
Units.dimensionless
|
||||
}
|
||||
}
|
||||
|
||||
override fun evaluate(values: Map<String, Double>): Double {
|
||||
val leftValue = left.evaluate(values)
|
||||
val rightValue = right.evaluate(values)
|
||||
return when (operator) {
|
||||
BinaryOperator.ADD -> leftValue + rightValue
|
||||
BinaryOperator.SUBTRACT -> leftValue - rightValue
|
||||
BinaryOperator.MULTIPLY -> leftValue * rightValue
|
||||
BinaryOperator.DIVIDE -> leftValue / rightValue
|
||||
BinaryOperator.POWER -> leftValue.pow(rightValue)
|
||||
BinaryOperator.GREATER_THAN -> if (leftValue > rightValue) 1.0 else 0.0
|
||||
BinaryOperator.LESS_THAN -> if (leftValue < rightValue) 1.0 else 0.0
|
||||
BinaryOperator.GREATER_OR_EQUAL -> if (leftValue >= rightValue) 1.0 else 0.0
|
||||
BinaryOperator.LESS_OR_EQUAL -> if (leftValue <= rightValue) 1.0 else 0.0
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkDimensions() {
|
||||
left.checkDimensions()
|
||||
right.checkDimensions()
|
||||
when (operator) {
|
||||
BinaryOperator.ADD, BinaryOperator.SUBTRACT -> {
|
||||
if (left.unit != right.unit) {
|
||||
throw IllegalArgumentException("Units must be the same for addition or subtraction.")
|
||||
}
|
||||
}
|
||||
BinaryOperator.MULTIPLY, BinaryOperator.DIVIDE -> {
|
||||
// Единицы измерения уже вычислены в `unit`
|
||||
}
|
||||
BinaryOperator.POWER -> {
|
||||
if (!right.unit.dimension.isDimensionless()) {
|
||||
throw IllegalArgumentException("Exponent must be dimensionless.")
|
||||
}
|
||||
}
|
||||
BinaryOperator.GREATER_THAN, BinaryOperator.LESS_THAN,
|
||||
BinaryOperator.GREATER_OR_EQUAL, BinaryOperator.LESS_OR_EQUAL -> {
|
||||
if (left.unit != right.unit) {
|
||||
throw IllegalArgumentException("Units must be the same for comparison operations.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun derivative(variable: VariableExpression): Expression {
|
||||
return when (operator) {
|
||||
BinaryOperator.ADD, BinaryOperator.SUBTRACT -> {
|
||||
BinaryExpression(left.derivative(variable), operator, right.derivative(variable))
|
||||
}
|
||||
BinaryOperator.MULTIPLY -> {
|
||||
// Правило произведения: (u*v)' = u'*v + u*v'
|
||||
val leftDerivative = BinaryExpression(left.derivative(variable), BinaryOperator.MULTIPLY, right)
|
||||
val rightDerivative = BinaryExpression(left, BinaryOperator.MULTIPLY, right.derivative(variable))
|
||||
BinaryExpression(leftDerivative, BinaryOperator.ADD, rightDerivative)
|
||||
}
|
||||
BinaryOperator.DIVIDE -> {
|
||||
// Правило частного: (u/v)' = (u'*v - u*v') / v^2
|
||||
val numerator = BinaryExpression(
|
||||
BinaryExpression(left.derivative(variable), BinaryOperator.MULTIPLY, right),
|
||||
BinaryOperator.SUBTRACT,
|
||||
BinaryExpression(left, BinaryOperator.MULTIPLY, right.derivative(variable))
|
||||
)
|
||||
val denominator = BinaryExpression(right, BinaryOperator.POWER, ConstantExpression(2.0, Units.dimensionless))
|
||||
BinaryExpression(numerator, BinaryOperator.DIVIDE, denominator)
|
||||
}
|
||||
BinaryOperator.POWER -> {
|
||||
if (right is ConstantExpression) {
|
||||
// (u^n)' = n * u^(n-1) * u'
|
||||
val n = right.value
|
||||
val newExponent = ConstantExpression(n - 1.0, Units.dimensionless)
|
||||
val baseDerivative = left.derivative(variable)
|
||||
val powerExpression = BinaryExpression(left, BinaryOperator.POWER, newExponent)
|
||||
BinaryExpression(
|
||||
BinaryExpression(ConstantExpression(n, Units.dimensionless), BinaryOperator.MULTIPLY, powerExpression),
|
||||
BinaryOperator.MULTIPLY,
|
||||
baseDerivative
|
||||
)
|
||||
} else {
|
||||
throw UnsupportedOperationException("Derivative of expression with variable exponent is not supported.")
|
||||
}
|
||||
}
|
||||
else -> throw UnsupportedOperationException("Derivative not implemented for operator $operator.")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
|
||||
/**
|
||||
* Класс для представления константного значения.
|
||||
*/
|
||||
public class ConstantExpression(public val value: Double, override val unit: PhysicalUnit) : Expression() {
|
||||
override fun evaluate(values: Map<String, Double>): Double {
|
||||
return value
|
||||
}
|
||||
override fun checkDimensions() {
|
||||
// Константа имеет единицу измерения, дополнительных проверок не требуется
|
||||
}
|
||||
|
||||
override fun derivative(variable: VariableExpression): Expression {
|
||||
return ConstantExpression(0.0, Units.dimensionless)
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
|
||||
/**
|
||||
* Класс для представления производной переменной по времени.
|
||||
*/
|
||||
public class DerivativeExpression(
|
||||
public val variable: VariableExpression,
|
||||
public val order: Int = 1 // Порядок производной (по умолчанию 1)
|
||||
) : Expression() {
|
||||
|
||||
override val unit: PhysicalUnit = variable.unit / Units.second.pow(order.toDouble())
|
||||
|
||||
override fun evaluate(values: Map<String, Double>): Double {
|
||||
// вычисление производной невозможно в этом контексте
|
||||
throw UnsupportedOperationException("Cannot evaluate derivative directly.")
|
||||
}
|
||||
|
||||
override fun checkDimensions() {
|
||||
variable.checkDimensions()
|
||||
// Единица измерения уже рассчитана
|
||||
}
|
||||
|
||||
override fun derivative(variable: VariableExpression): Expression {
|
||||
// Производная от производной - это производная более высокого порядка
|
||||
return DerivativeExpression(this.variable, this.order + 1)
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
|
||||
/**
|
||||
* Абстрактный класс для представления математических выражений.
|
||||
* Каждое выражение связано с единицей измерения.
|
||||
*/
|
||||
public abstract class Expression {
|
||||
public abstract val unit: PhysicalUnit
|
||||
|
||||
/**
|
||||
* Метод для вычисления численного значения выражения.
|
||||
* Параметр values содержит значения переменных.
|
||||
*/
|
||||
public abstract fun evaluate(values: Map<String, Double>): Double
|
||||
|
||||
/**
|
||||
* Проверка размерности выражения.
|
||||
*/
|
||||
public abstract fun checkDimensions()
|
||||
|
||||
/**
|
||||
* Упрощение выражения.
|
||||
*/
|
||||
public open fun simplify(): Expression = this
|
||||
|
||||
/**
|
||||
* Производная выражения по заданной переменной.
|
||||
*/
|
||||
public open fun derivative(variable: VariableExpression): Expression {
|
||||
throw UnsupportedOperationException("Derivative not implemented for this expression type.")
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import kotlin.math.*
|
||||
|
||||
/**
|
||||
* Класс для представления вызова математической функции.
|
||||
*/
|
||||
public class FunctionCallExpression(
|
||||
public val functionName: String,
|
||||
public val arguments: List<Expression>
|
||||
) : Expression() {
|
||||
|
||||
override val unit: PhysicalUnit = determineUnit()
|
||||
|
||||
private fun determineUnit(): PhysicalUnit {
|
||||
return when (functionName) {
|
||||
"sin", "cos", "tan", "asin", "acos", "atan",
|
||||
"sinh", "cosh", "tanh", "asinh", "acosh", "atanh",
|
||||
"exp", "log", "log10", "signum" -> Units.dimensionless
|
||||
"sqrt", "cbrt", "abs" -> arguments[0].unit
|
||||
"pow", "max", "min" -> arguments[0].unit
|
||||
else -> throw IllegalArgumentException("Unknown function: $functionName")
|
||||
}
|
||||
}
|
||||
|
||||
override fun evaluate(values: Map<String, Double>): Double {
|
||||
val argValues = arguments.map { it.evaluate(values) }
|
||||
return when (functionName) {
|
||||
"sin" -> sin(argValues[0])
|
||||
"cos" -> cos(argValues[0])
|
||||
"tan" -> tan(argValues[0])
|
||||
"asin" -> asin(argValues[0])
|
||||
"acos" -> acos(argValues[0])
|
||||
"atan" -> atan(argValues[0])
|
||||
"sinh" -> sinh(argValues[0])
|
||||
"cosh" -> cosh(argValues[0])
|
||||
"tanh" -> tanh(argValues[0])
|
||||
"asinh" -> asinh(argValues[0])
|
||||
"acosh" -> acosh(argValues[0])
|
||||
"atanh" -> atanh(argValues[0])
|
||||
"exp" -> exp(argValues[0])
|
||||
"log" -> ln(argValues[0])
|
||||
"log10" -> log10(argValues[0])
|
||||
"sqrt" -> sqrt(argValues[0])
|
||||
"cbrt" -> cbrt(argValues[0])
|
||||
"abs" -> abs(argValues[0])
|
||||
"signum" -> sign(argValues[0])
|
||||
"pow" -> argValues[0].pow(argValues[1])
|
||||
"max" -> max(argValues[0], argValues[1])
|
||||
"min" -> min(argValues[0], argValues[1])
|
||||
else -> throw IllegalArgumentException("Unknown function: $functionName")
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkDimensions() {
|
||||
when (functionName) {
|
||||
"sin", "cos", "tan", "asin", "acos", "atan",
|
||||
"sinh", "cosh", "tanh", "asinh", "acosh", "atanh",
|
||||
"exp", "log", "log10", "signum" -> {
|
||||
val argUnit = arguments[0].unit
|
||||
if (!argUnit.dimension.isDimensionless()) {
|
||||
throw IllegalArgumentException("Argument of $functionName must be dimensionless.")
|
||||
}
|
||||
}
|
||||
"sqrt", "cbrt", "abs" -> {
|
||||
// Единица измерения сохраняется, дополнительных проверок не требуется
|
||||
}
|
||||
"pow" -> {
|
||||
val exponentUnit = arguments[1].unit
|
||||
if (!exponentUnit.dimension.isDimensionless()) {
|
||||
throw IllegalArgumentException("Exponent in $functionName must be dimensionless.")
|
||||
}
|
||||
}
|
||||
"max", "min" -> {
|
||||
if (arguments[0].unit != arguments[1].unit) {
|
||||
throw IllegalArgumentException("Arguments of $functionName must have the same units.")
|
||||
}
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unknown function: $functionName")
|
||||
}
|
||||
// Проверяем размерности аргументов
|
||||
arguments.forEach { it.checkDimensions() }
|
||||
}
|
||||
|
||||
override fun derivative(variable: VariableExpression): Expression {
|
||||
// Реализация производной для функций
|
||||
throw UnsupportedOperationException("Derivative for function $functionName is not implemented.")
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
|
||||
/**
|
||||
* Представляет частную производную переменной по другой переменной.
|
||||
*/
|
||||
public class PartialDerivativeExpression(
|
||||
public val variable: VariableExpression,
|
||||
public val respectTo: VariableExpression,
|
||||
public val order: Int = 1
|
||||
) : Expression() {
|
||||
|
||||
override val unit: PhysicalUnit = variable.unit / respectTo.unit.pow(order.toDouble())
|
||||
|
||||
override fun evaluate(values: Map<String, Double>): Double {
|
||||
// Невозможно вычислить частную производную без дополнительного контекста
|
||||
throw UnsupportedOperationException("Cannot evaluate partial derivative directly.")
|
||||
}
|
||||
|
||||
override fun checkDimensions() {
|
||||
variable.checkDimensions()
|
||||
respectTo.checkDimensions()
|
||||
// Единицы измерения уже рассчитаны
|
||||
}
|
||||
|
||||
override fun derivative(variable: VariableExpression): Expression {
|
||||
// Производная частной производной не реализована
|
||||
throw UnsupportedOperationException("Derivative of partial derivative is not implemented.")
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Класс для представления переменной в выражении.
|
||||
*/
|
||||
public class VariableExpression(public val variable: Variable) : Expression() {
|
||||
override val unit: PhysicalUnit = variable.unit
|
||||
|
||||
override fun evaluate(values: Map<String, Double>): Double {
|
||||
return values[variable.name] ?: throw IllegalArgumentException("Variable ${variable.name} is not defined.")
|
||||
}
|
||||
|
||||
override fun checkDimensions() {
|
||||
// Переменная уже имеет единицу измерения, дополнительных проверок не требуется
|
||||
}
|
||||
|
||||
override fun derivative(variable: VariableExpression): Expression {
|
||||
return if (this.variable == variable.variable) {
|
||||
ConstantExpression(1.0, Units.dimensionless)
|
||||
} else {
|
||||
ConstantExpression(0.0, Units.dimensionless)
|
||||
}
|
||||
}
|
||||
|
||||
public fun partialDerivative(respectTo: VariableExpression, order: Int = 1): PartialDerivativeExpression {
|
||||
return PartialDerivativeExpression(this, respectTo, order)
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.functions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.algorithms.Algorithm
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Класс для представления пользовательской функции.
|
||||
*/
|
||||
public data class Function(
|
||||
val name: String,
|
||||
val inputs: List<Variable>,
|
||||
val outputs: List<Variable>,
|
||||
val algorithm: Algorithm
|
||||
)
|
@ -1,50 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.functions
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.algorithms.AlgorithmBuilder
|
||||
import space.kscience.controls.constructor.dsl.core.algorithms.Algorithm
|
||||
import space.kscience.controls.constructor.dsl.core.components.Model
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Causality
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Класс для построения пользовательской функции.
|
||||
*/
|
||||
public class FunctionBuilder(private val name: String) {
|
||||
public val inputs: MutableList<Variable> = mutableListOf<Variable>()
|
||||
public val outputs: MutableList<Variable> = mutableListOf<Variable>()
|
||||
private val algorithmBuilder = AlgorithmBuilder()
|
||||
|
||||
/**
|
||||
* Определение входного параметра функции.
|
||||
*/
|
||||
public fun input(name: String, unit: PhysicalUnit): Variable {
|
||||
val variable = Variable(name, unit, causality = Causality.INPUT)
|
||||
inputs.add(variable)
|
||||
return variable
|
||||
}
|
||||
|
||||
/**
|
||||
* Определение выходного параметра функции.
|
||||
*/
|
||||
public fun output(name: String, unit: PhysicalUnit): Variable {
|
||||
val variable = Variable(name, unit, causality = Causality.OUTPUT)
|
||||
outputs.add(variable)
|
||||
return variable
|
||||
}
|
||||
|
||||
/**
|
||||
* Определение алгоритмического тела функции.
|
||||
*/
|
||||
public fun algorithm(init: AlgorithmBuilder.() -> Unit) {
|
||||
algorithmBuilder.init()
|
||||
}
|
||||
|
||||
/**
|
||||
* Построение функции.
|
||||
*/
|
||||
public fun build(): Function {
|
||||
val algorithm = algorithmBuilder.build()
|
||||
return Function(name, inputs, outputs, algorithm)
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.simulation
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import space.kscience.controls.constructor.dsl.core.EquationSystem
|
||||
import space.kscience.controls.constructor.dsl.core.events.Event
|
||||
|
||||
public interface NumericSolver {
|
||||
/**
|
||||
* Инициализирует решатель.
|
||||
*/
|
||||
public suspend fun initialize(
|
||||
equationSystem: EquationSystem,
|
||||
context: SimulationContext
|
||||
)
|
||||
|
||||
/**
|
||||
* Выполняет один шаг симуляции.
|
||||
*/
|
||||
public suspend fun step(context: SimulationContext, timeStep: Double): Boolean
|
||||
|
||||
/**
|
||||
* Запускает симуляцию.
|
||||
*/
|
||||
public suspend fun solve(
|
||||
equationSystem: EquationSystem,
|
||||
context: SimulationContext,
|
||||
timeStart: Double,
|
||||
timeEnd: Double,
|
||||
timeStep: Double
|
||||
): SimulationResult
|
||||
}
|
||||
|
||||
public data class SimulationResult(
|
||||
val timePoints: List<Double>,
|
||||
val variableValues: Map<String, List<Double>>
|
||||
)
|
@ -1,93 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.simulation
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.controls.constructor.dsl.core.events.Event
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
/**
|
||||
* Класс для хранения состояния симуляции.
|
||||
*/
|
||||
public class SimulationContext(
|
||||
private val scope: CoroutineScope
|
||||
) {
|
||||
public var currentTime: Double = 0.0
|
||||
private val _variableValues: MutableMap<String, MutableStateFlow<Double>> = mutableMapOf()
|
||||
|
||||
// Канал для событий
|
||||
private val eventChannel = Channel<Event>()
|
||||
|
||||
// Список событий
|
||||
public val events: MutableList<Event> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Регистрирует переменную в контексте симуляции.
|
||||
*/
|
||||
public fun registerVariable(variable: Variable) {
|
||||
_variableValues[variable.name] = MutableStateFlow(variable.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновляет значение переменной.
|
||||
*/
|
||||
public suspend fun updateVariable(name: String, value: Double) {
|
||||
_variableValues[name]?.emit(value)
|
||||
?: throw IllegalArgumentException("Variable $name is not defined in the simulation context.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает текущее значение переменной.
|
||||
*/
|
||||
public fun getCurrentVariableValue(name: String): Double {
|
||||
return _variableValues[name]?.value
|
||||
?: throw IllegalArgumentException("Variable $name is not defined in the simulation context.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает текущие значения всех переменных.
|
||||
*/
|
||||
public fun getVariableValues(): Map<String, Double> {
|
||||
return _variableValues.mapValues { it.value.value }
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправляет событие на обработку.
|
||||
*/
|
||||
public suspend fun sendEvent(event: Event) {
|
||||
eventChannel.send(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Запускает обработку событий.
|
||||
*/
|
||||
public fun startEventHandling() {
|
||||
scope.launch {
|
||||
for (event in eventChannel) {
|
||||
if (event.checkEvent(this@SimulationContext)) {
|
||||
event.executeActions(this@SimulationContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает события из списка events.
|
||||
*/
|
||||
public suspend fun handleEvents() {
|
||||
for (event in events) {
|
||||
if (event.checkEvent(this)) {
|
||||
event.executeActions(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Увеличивает время симуляции.
|
||||
*/
|
||||
public suspend fun advanceTime(deltaTime: Double) {
|
||||
currentTime += deltaTime
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.units
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Класс для представления размерности физической величины.
|
||||
*/
|
||||
public data class Dimension(
|
||||
val length: Double = 0.0,
|
||||
val mass: Double = 0.0,
|
||||
val time: Double = 0.0,
|
||||
val electricCurrent: Double = 0.0,
|
||||
val temperature: Double = 0.0,
|
||||
val amountOfSubstance: Double = 0.0,
|
||||
val luminousIntensity: Double = 0.0
|
||||
) {
|
||||
public operator fun plus(other: Dimension): Dimension {
|
||||
return Dimension(
|
||||
length + other.length,
|
||||
mass + other.mass,
|
||||
time + other.time,
|
||||
electricCurrent + other.electricCurrent,
|
||||
temperature + other.temperature,
|
||||
amountOfSubstance + other.amountOfSubstance,
|
||||
luminousIntensity + other.luminousIntensity
|
||||
)
|
||||
}
|
||||
|
||||
public operator fun minus(other: Dimension): Dimension {
|
||||
return Dimension(
|
||||
length - other.length,
|
||||
mass - other.mass,
|
||||
time - other.time,
|
||||
electricCurrent - other.electricCurrent,
|
||||
temperature - other.temperature,
|
||||
amountOfSubstance - other.amountOfSubstance,
|
||||
luminousIntensity - other.luminousIntensity
|
||||
)
|
||||
}
|
||||
|
||||
public operator fun times(factor: Double): Dimension {
|
||||
return Dimension(
|
||||
length * factor,
|
||||
mass * factor,
|
||||
time * factor,
|
||||
electricCurrent * factor,
|
||||
temperature * factor,
|
||||
amountOfSubstance * factor,
|
||||
luminousIntensity * factor
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, является ли размерность безразмерной.
|
||||
*/
|
||||
public fun isDimensionless(epsilon: Double = 1e-10): Boolean {
|
||||
return abs(length) < epsilon &&
|
||||
abs(mass) < epsilon &&
|
||||
abs(time) < epsilon &&
|
||||
abs(electricCurrent) < epsilon &&
|
||||
abs(temperature) < epsilon &&
|
||||
abs(amountOfSubstance) < epsilon &&
|
||||
abs(luminousIntensity) < epsilon
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет совместимость размерностей.
|
||||
*/
|
||||
public fun isCompatibleWith(other: Dimension, epsilon: Double = 1e-10): Boolean {
|
||||
return abs(length - other.length) < epsilon &&
|
||||
abs(mass - other.mass) < epsilon &&
|
||||
abs(time - other.time) < epsilon &&
|
||||
abs(electricCurrent - other.electricCurrent) < epsilon &&
|
||||
abs(temperature - other.temperature) < epsilon &&
|
||||
abs(amountOfSubstance - other.amountOfSubstance) < epsilon &&
|
||||
abs(luminousIntensity - other.luminousIntensity) < epsilon
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.units
|
||||
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* Класс для представления физической единицы измерения.
|
||||
*/
|
||||
public data class PhysicalUnit(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val conversionFactorToSI: Double,
|
||||
val dimension: Dimension
|
||||
) {
|
||||
public operator fun times(other: PhysicalUnit): PhysicalUnit {
|
||||
return PhysicalUnit(
|
||||
name = "$name·${other.name}",
|
||||
symbol = "$symbol·${other.symbol}",
|
||||
conversionFactorToSI = this.conversionFactorToSI * other.conversionFactorToSI,
|
||||
dimension = this.dimension + other.dimension
|
||||
)
|
||||
}
|
||||
|
||||
public operator fun div(other: PhysicalUnit): PhysicalUnit {
|
||||
return PhysicalUnit(
|
||||
name = "$name/${other.name}",
|
||||
symbol = "$symbol/${other.symbol}",
|
||||
conversionFactorToSI = this.conversionFactorToSI / other.conversionFactorToSI,
|
||||
dimension = this.dimension - other.dimension
|
||||
)
|
||||
}
|
||||
|
||||
public fun pow(exponent: Double): PhysicalUnit {
|
||||
return PhysicalUnit(
|
||||
name = "$name^$exponent",
|
||||
symbol = "${symbol}^$exponent",
|
||||
conversionFactorToSI = this.conversionFactorToSI.pow(exponent),
|
||||
dimension = this.dimension * exponent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразует значение из текущей единицы в целевую единицу.
|
||||
*/
|
||||
public fun convertValueTo(valueInThisUnit: Double, targetUnit: PhysicalUnit): Double {
|
||||
require(this.dimension.isCompatibleWith(targetUnit.dimension)) {
|
||||
"Units are not compatible: $this and $targetUnit"
|
||||
}
|
||||
val valueInSI = valueInThisUnit * this.conversionFactorToSI
|
||||
return valueInSI / targetUnit.conversionFactorToSI
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.units
|
||||
|
||||
import kotlin.math.*
|
||||
|
||||
public object Units {
|
||||
private val unitsRegistry = mutableMapOf<String, PhysicalUnit>()
|
||||
|
||||
// Основные единицы СИ
|
||||
public val meter: PhysicalUnit = PhysicalUnit("meter", "m", 1.0, Dimension(length = 1.0))
|
||||
public val kilogram: PhysicalUnit = PhysicalUnit("kilogram", "kg", 1.0, Dimension(mass = 1.0))
|
||||
public val second: PhysicalUnit = PhysicalUnit("second", "s", 1.0, Dimension(time = 1.0))
|
||||
public val ampere: PhysicalUnit = PhysicalUnit("ampere", "A", 1.0, Dimension(electricCurrent = 1.0))
|
||||
public val kelvin: PhysicalUnit = PhysicalUnit("kelvin", "K", 1.0, Dimension(temperature = 1.0))
|
||||
public val mole: PhysicalUnit = PhysicalUnit("mole", "mol", 1.0, Dimension(amountOfSubstance = 1.0))
|
||||
public val candela: PhysicalUnit = PhysicalUnit("candela", "cd", 1.0, Dimension(luminousIntensity = 1.0))
|
||||
public val radian: PhysicalUnit = PhysicalUnit("radian", "rad", 1.0, Dimension())
|
||||
public val hertz: PhysicalUnit = PhysicalUnit("hertz", "Hz", 1.0, Dimension(time = -1.0))
|
||||
|
||||
public val dimensionless: PhysicalUnit = PhysicalUnit("dimensionless", "", 1.0, Dimension())
|
||||
|
||||
init {
|
||||
// Регистрируем основные единицы
|
||||
registerUnit(meter)
|
||||
registerUnit(kilogram)
|
||||
registerUnit(second)
|
||||
registerUnit(ampere)
|
||||
registerUnit(kelvin)
|
||||
registerUnit(mole)
|
||||
registerUnit(candela)
|
||||
registerUnit(radian)
|
||||
registerUnit(hertz)
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрирует единицу измерения в реестре.
|
||||
*/
|
||||
public fun registerUnit(unit: PhysicalUnit) {
|
||||
unitsRegistry[unit.name] = unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает единицу измерения по имени.
|
||||
*/
|
||||
public fun getUnit(name: String): PhysicalUnit? {
|
||||
return unitsRegistry[name]
|
||||
}
|
||||
|
||||
/**
|
||||
* Список префиксов СИ.
|
||||
*/
|
||||
public enum class Prefix(public val prefixName: String, public val symbol: String, public val factor: Double) {
|
||||
YOTTA("yotta", "Y", 1e24),
|
||||
ZETTA("zetta", "Z", 1e21),
|
||||
EXA("exa", "E", 1e18),
|
||||
PETA("peta", "P", 1e15),
|
||||
TERA("tera", "T", 1e12),
|
||||
GIGA("giga", "G", 1e9),
|
||||
MEGA("mega", "M", 1e6),
|
||||
KILO("kilo", "k", 1e3),
|
||||
HECTO("hecto", "h", 1e2),
|
||||
DECA("deca", "da", 1e1),
|
||||
DECI("deci", "d", 1e-1),
|
||||
CENTI("centi", "c", 1e-2),
|
||||
MILLI("milli", "m", 1e-3),
|
||||
MICRO("micro", "μ", 1e-6),
|
||||
NANO("nano", "n", 1e-9),
|
||||
PICO("pico", "p", 1e-12),
|
||||
FEMTO("femto", "f", 1e-15),
|
||||
ATTO("atto", "a", 1e-18),
|
||||
ZEPTO("zepto", "z", 1e-21),
|
||||
YOCTO("yocto", "y", 1e-24)
|
||||
}
|
||||
|
||||
/**
|
||||
* Создает новую единицу с заданным префиксом.
|
||||
*/
|
||||
public fun withPrefix(prefix: Prefix, unit: PhysicalUnit): PhysicalUnit {
|
||||
val newName = "${prefix.prefixName}${unit.name}"
|
||||
val newSymbol = "${prefix.symbol}${unit.symbol}"
|
||||
val newConversionFactor = unit.conversionFactorToSI * prefix.factor
|
||||
val newUnit = PhysicalUnit(newName, newSymbol, newConversionFactor, unit.dimension)
|
||||
registerUnit(newUnit)
|
||||
return newUnit
|
||||
}
|
||||
|
||||
public val kilometer: PhysicalUnit = withPrefix(Prefix.KILO, meter)
|
||||
public val millimeter: PhysicalUnit = withPrefix(Prefix.MILLI, meter)
|
||||
public val microsecond: PhysicalUnit = withPrefix(Prefix.MICRO, second)
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
|
||||
/**
|
||||
* Абстрактный класс для переменных.
|
||||
*/
|
||||
public abstract class AbstractVariable(
|
||||
public val name: String,
|
||||
public val unit: PhysicalUnit,
|
||||
public val variability: Variability,
|
||||
public val causality: Causality
|
||||
)
|
@ -1,28 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import org.jetbrains.kotlinx.multik.ndarray.data.D2
|
||||
import org.jetbrains.kotlinx.multik.ndarray.data.NDArray
|
||||
import org.jetbrains.kotlinx.multik.ndarray.data.set
|
||||
import org.jetbrains.kotlinx.multik.ndarray.operations.plus
|
||||
|
||||
/**
|
||||
* Класс для представления переменной-массива.
|
||||
* Использует Multik для работы с многомерными массивами.
|
||||
*/
|
||||
public class ArrayVariable(
|
||||
name: String,
|
||||
unit: PhysicalUnit,
|
||||
public val array: NDArray<Double, D2>,
|
||||
causality: Causality = Causality.INTERNAL
|
||||
) : AbstractVariable(name, unit, Variability.CONTINUOUS, causality) {
|
||||
|
||||
public fun add(other: ArrayVariable): ArrayVariable {
|
||||
require(this.array.shape contentEquals other.array.shape) {
|
||||
"Array shapes do not match."
|
||||
}
|
||||
val resultArray = this.array + other.array
|
||||
return ArrayVariable("$name + ${other.name}", unit, resultArray, causality)
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
/**
|
||||
* Перечисление каузальности переменной.
|
||||
*/
|
||||
public enum class Causality {
|
||||
INPUT,
|
||||
OUTPUT,
|
||||
INTERNAL
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
|
||||
/**
|
||||
* Класс для представления константы.
|
||||
*/
|
||||
public class ConstantVariable(
|
||||
name: String,
|
||||
unit: PhysicalUnit,
|
||||
public val value: Double
|
||||
) : AbstractVariable(name, unit, Variability.CONSTANT, Causality.INTERNAL)
|
@ -1,22 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
|
||||
public class ContinuousVariable(
|
||||
name: String,
|
||||
unit: PhysicalUnit,
|
||||
public var value: Double = 0.0,
|
||||
public val min: Double? = null,
|
||||
public val max: Double? = null,
|
||||
causality: Causality = Causality.INTERNAL
|
||||
) : AbstractVariable(name, unit, Variability.CONTINUOUS, causality) {
|
||||
init {
|
||||
// Проверка допустимого диапазона
|
||||
if (min != null && value < min) {
|
||||
throw IllegalArgumentException("Value of $name is less than minimum allowed value.")
|
||||
}
|
||||
if (max != null && value > max) {
|
||||
throw IllegalArgumentException("Value of $name is greater than maximum allowed value.")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
|
||||
/**
|
||||
* Класс для представления дискретной переменной.
|
||||
*/
|
||||
public class DiscreteVariable(
|
||||
name: String,
|
||||
unit: PhysicalUnit,
|
||||
public var value: Double = 0.0,
|
||||
causality: Causality = Causality.INTERNAL
|
||||
) : AbstractVariable(name, unit, Variability.DISCRETE, causality)
|
@ -1,25 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
|
||||
/**
|
||||
* Класс для представления параметра (неизменяемой величины).
|
||||
*/
|
||||
public open class ParameterVariable(
|
||||
name: String,
|
||||
unit: PhysicalUnit,
|
||||
public val value: Double,
|
||||
public val min: Double? = null,
|
||||
public val max: Double? = null,
|
||||
causality: Causality = Causality.INTERNAL
|
||||
) : AbstractVariable(name, unit, Variability.PARAMETER, causality) {
|
||||
init {
|
||||
// Проверка допустимого диапазона
|
||||
if (min != null && value < min) {
|
||||
throw IllegalArgumentException("Value of parameter $name is less than minimum allowed value.")
|
||||
}
|
||||
if (max != null && value > max) {
|
||||
throw IllegalArgumentException("Value of parameter $name is greater than maximum allowed value.")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
/**
|
||||
* Перечисление типов изменчивости переменной.
|
||||
*/
|
||||
public enum class Variability {
|
||||
CONSTANT,
|
||||
PARAMETER,
|
||||
DISCRETE,
|
||||
CONTINUOUS
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
|
||||
/**
|
||||
* Класс для представления переменной.
|
||||
*/
|
||||
public open class Variable(
|
||||
public val name: String,
|
||||
public val unit: PhysicalUnit,
|
||||
public open var value: Double = 0.0,
|
||||
public val min: Double? = null,
|
||||
public val max: Double? = null,
|
||||
public var variability: Variability = Variability.CONTINUOUS,
|
||||
public var causality: Causality = Causality.INTERNAL
|
||||
) {
|
||||
init {
|
||||
// Проверка, что значение находится в допустимом диапазоне
|
||||
if (min != null && value < min) {
|
||||
throw IllegalArgumentException("Value of $name is less than minimum allowed value.")
|
||||
}
|
||||
if (max != null && value > max) {
|
||||
throw IllegalArgumentException("Value of $name is greater than maximum allowed value.")
|
||||
}
|
||||
}
|
||||
|
||||
public open fun checkDimensions() {}
|
||||
}
|
@ -55,7 +55,7 @@ public class Inertia<U : UnitsOfMeasurement, V : UnitsOfMeasurement>(
|
||||
|
||||
public fun circular(
|
||||
context: Context,
|
||||
force: DeviceState<NumericalValue<NewtonsMeters>>,
|
||||
force: DeviceState<NumericalValue<NewtonMeters>>,
|
||||
momentOfInertia: NumericalValue<KgM2>,
|
||||
position: MutableDeviceState<NumericalValue<Degrees>>,
|
||||
velocity: MutableDeviceState<NumericalValue<DegreesPerSecond>> = MutableDeviceState(NumericalValue(0.0)),
|
||||
|
@ -16,7 +16,7 @@ public class Leadscrew(
|
||||
) : ModelConstructor(context) {
|
||||
|
||||
public fun torqueToForce(
|
||||
stateOfTorque: DeviceState<NumericalValue<NewtonsMeters>>,
|
||||
stateOfTorque: DeviceState<NumericalValue<NewtonMeters>>,
|
||||
): DeviceState<NumericalValue<Newtons>> = DeviceState.map(stateOfTorque) { torque ->
|
||||
NumericalValue(torque.value / leverage.value )
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import space.kscience.dataforge.meta.MetaConverter
|
||||
import space.kscience.dataforge.meta.double
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
|
||||
/**
|
||||
* A value without identity coupled to units of measurements.
|
||||
*/
|
||||
|
@ -1,60 +1,244 @@
|
||||
package space.kscience.controls.constructor.units
|
||||
|
||||
import kotlin.math.*
|
||||
|
||||
public interface UnitsOfMeasurement
|
||||
/**
|
||||
* Represents a unit of measurement.
|
||||
* Provides methods to convert values to and from the base unit.
|
||||
*/
|
||||
public interface UnitsOfMeasurement {
|
||||
/**
|
||||
* Symbol representing the unit (e.g., "m" for meters).
|
||||
*/
|
||||
public val symbol: String
|
||||
|
||||
/**/
|
||||
/**
|
||||
* Converts a value from this unit to the base unit.
|
||||
*/
|
||||
public fun toBase(value: Double): Double
|
||||
altavir
commented
Not clear how we select the base unit. Probably better documentation is needed. Is it better than the external dispatch that is used right now? Not clear how we select the base unit. Probably better documentation is needed. Is it better than the external dispatch that is used right now?
|
||||
|
||||
/**
|
||||
* Converts a value from the base unit to this unit.
|
||||
*/
|
||||
public fun fromBase(value: Double): Double
|
||||
}
|
||||
|
||||
/**
|
||||
* Base unit, where conversion to base unit is identity.
|
||||
*/
|
||||
public open class BaseUnit(
|
||||
override val symbol: String,
|
||||
) : UnitsOfMeasurement {
|
||||
override fun toBase(value: Double): Double = value
|
||||
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived unit with a conversion factor to the base unit.
|
||||
*/
|
||||
public class DerivedUnit(
|
||||
override val symbol: String,
|
||||
private val conversionFactor: Double, // Factor to convert to base unit.
|
||||
) : UnitsOfMeasurement {
|
||||
override fun toBase(value: Double): Double = value * conversionFactor
|
||||
|
||||
override fun fromBase(value: Double): Double = value / conversionFactor
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of SI prefixes with their symbols and factors.
|
||||
*/
|
||||
public enum class SIPrefix(
|
||||
public val symbol: String,
|
||||
public val factor: Double
|
||||
) {
|
||||
YOTTA("Y", 1e24),
|
||||
ZETTA("Z", 1e21),
|
||||
EXA("E", 1e18),
|
||||
PETA("P", 1e15),
|
||||
TERA("T", 1e12),
|
||||
GIGA("G", 1e9),
|
||||
MEGA("M", 1e6),
|
||||
KILO("k", 1e3),
|
||||
HECTO("h", 1e2),
|
||||
DECA("da", 1e1),
|
||||
NONE("", 1.0),
|
||||
DECI("d", 1e-1),
|
||||
CENTI("c", 1e-2),
|
||||
MILLI("m", 1e-3),
|
||||
MICRO("μ", 1e-6),
|
||||
NANO("n", 1e-9),
|
||||
PICO("p", 1e-12),
|
||||
FEMTO("f", 1e-15),
|
||||
ATTO("a", 1e-18),
|
||||
ZEPTO("z", 1e-21),
|
||||
YOCTO("y", 1e-24),
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new unit by applying an SI prefix to the current unit.
|
||||
*/
|
||||
public fun <U : UnitsOfMeasurement> U.withPrefix(prefix: SIPrefix): UnitsOfMeasurement {
|
||||
val prefixedSymbol = prefix.symbol + this.symbol
|
||||
val prefixedFactor = prefix.factor
|
||||
|
||||
return object : UnitsOfMeasurement {
|
||||
override val symbol: String = prefixedSymbol
|
||||
|
||||
override fun toBase(value: Double): Double = this@withPrefix.toBase(value * prefixedFactor)
|
||||
|
||||
override fun fromBase(value: Double): Double = this@withPrefix.fromBase(value) / prefixedFactor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for units of length.
|
||||
*/
|
||||
public interface UnitsOfLength : UnitsOfMeasurement
|
||||
|
||||
public data object Meters : UnitsOfLength
|
||||
/**
|
||||
* Base unit for length: Meter.
|
||||
*/
|
||||
public data object Meters : UnitsOfLength {
|
||||
override val symbol: String = "m"
|
||||
override fun toBase(value: Double): Double = value // Base unit
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
||||
|
||||
/**/
|
||||
|
||||
/**
|
||||
* Interface for units of time.
|
||||
*/
|
||||
public interface UnitsOfTime : UnitsOfMeasurement
|
||||
|
||||
public data object Seconds : UnitsOfTime
|
||||
|
||||
/**/
|
||||
/**
|
||||
* Base unit for time: Second.
|
||||
*/
|
||||
public data object Seconds : UnitsOfTime {
|
||||
override val symbol: String = "s"
|
||||
override fun toBase(value: Double): Double = value // Base unit
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for units of velocity.
|
||||
*/
|
||||
public interface UnitsOfVelocity : UnitsOfMeasurement
|
||||
|
||||
public data object MetersPerSecond : UnitsOfVelocity
|
||||
/**
|
||||
* Derived unit for velocity: Meters per Second.
|
||||
*/
|
||||
public data object MetersPerSecond : UnitsOfVelocity {
|
||||
override val symbol: String = "m/s"
|
||||
override fun toBase(value: Double): Double = value // Base unit for velocity
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
||||
|
||||
/**/
|
||||
|
||||
/**
|
||||
* Sealed interface for units of angles.
|
||||
*/
|
||||
public sealed interface UnitsOfAngles : UnitsOfMeasurement
|
||||
|
||||
public data object Radians : UnitsOfAngles
|
||||
public data object Degrees : UnitsOfAngles
|
||||
/**
|
||||
* Base unit for angles: Radian.
|
||||
*/
|
||||
public data object Radians : UnitsOfAngles {
|
||||
override val symbol: String = "rad"
|
||||
override fun toBase(value: Double): Double = value // Base unit
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit for angles: Degree.
|
||||
*/
|
||||
public data object Degrees : UnitsOfAngles {
|
||||
override val symbol: String = "deg"
|
||||
override fun toBase(value: Double): Double = value * (PI / 180.0)
|
||||
override fun fromBase(value: Double): Double = value * (180.0 / PI)
|
||||
}
|
||||
|
||||
/**/
|
||||
|
||||
/**
|
||||
* Sealed interface for units of angular velocity.
|
||||
*/
|
||||
public sealed interface UnitsAngularOfVelocity : UnitsOfMeasurement
|
||||
|
||||
public data object RadiansPerSecond : UnitsAngularOfVelocity
|
||||
/**
|
||||
* Base unit for angular velocity: Radians per Second.
|
||||
*/
|
||||
public data object RadiansPerSecond : UnitsAngularOfVelocity {
|
||||
override val symbol: String = "rad/s"
|
||||
override fun toBase(value: Double): Double = value // Base unit
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
||||
|
||||
public data object DegreesPerSecond : UnitsAngularOfVelocity
|
||||
/**
|
||||
* Unit for angular velocity: Degrees per Second.
|
||||
*/
|
||||
public data object DegreesPerSecond : UnitsAngularOfVelocity {
|
||||
override val symbol: String = "deg/s"
|
||||
override fun toBase(value: Double): Double = value * (PI / 180.0)
|
||||
override fun fromBase(value: Double): Double = value * (180.0 / PI)
|
||||
}
|
||||
|
||||
/**/
|
||||
/**
|
||||
* Interface for units of force.
|
||||
*/
|
||||
public interface UnitsOfForce : UnitsOfMeasurement
|
||||
|
||||
public data object Newtons: UnitsOfForce
|
||||
|
||||
/**/
|
||||
/**
|
||||
* Base unit for force: Newton.
|
||||
*/
|
||||
public data object Newtons : UnitsOfForce {
|
||||
override val symbol: String = "N"
|
||||
override fun toBase(value: Double): Double = value // Base unit
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for units of torque.
|
||||
*/
|
||||
public interface UnitsOfTorque : UnitsOfMeasurement
|
||||
|
||||
public data object NewtonsMeters: UnitsOfTorque
|
||||
|
||||
/**/
|
||||
/**
|
||||
* Base unit for torque: Newton Meter.
|
||||
*/
|
||||
public data object NewtonMeters : UnitsOfTorque {
|
||||
override val symbol: String = "N·m"
|
||||
override fun toBase(value: Double): Double = value // Base unit
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for units of mass.
|
||||
*/
|
||||
public interface UnitsOfMass : UnitsOfMeasurement
|
||||
|
||||
public data object Kilograms : UnitsOfMass
|
||||
|
||||
/**/
|
||||
/**
|
||||
* Base unit for mass: Kilogram.
|
||||
*/
|
||||
public data object Kilograms : UnitsOfMass {
|
||||
override val symbol: String = "kg"
|
||||
override fun toBase(value: Double): Double = value // Base unit
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for units of moment of inertia.
|
||||
*/
|
||||
public interface UnitsOfMomentOfInertia : UnitsOfMeasurement
|
||||
|
||||
public data object KgM2: UnitsOfMomentOfInertia
|
||||
/**
|
||||
* Base unit for moment of inertia: Kilogram Meter Squared.
|
||||
*/
|
||||
public data object KgM2 : UnitsOfMomentOfInertia {
|
||||
override val symbol: String = "kg·m²"
|
||||
override fun toBase(value: Double): Double = value // Base unit
|
||||
override fun fromBase(value: Double): Double = value
|
||||
}
|
@ -0,0 +1,806 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import space.kscience.controls.api.LifecycleState
|
||||
import space.kscience.controls.spec.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
// Спецификация шагового мотора
|
||||
object StepperMotorSpec : DeviceSpec<StepperMotorDevice>() {
|
||||
|
||||
val position by intProperty(
|
||||
read = { propertyName -> getPosition() },
|
||||
write = { propertyName, value -> setPosition(value) }
|
||||
)
|
||||
|
||||
val maxPosition by intProperty(
|
||||
read = { propertyName -> maxPosition }
|
||||
)
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): StepperMotorDevice {
|
||||
return StepperMotorDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация устройства шагового мотора
|
||||
class StepperMotorDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<StepperMotorDevice>(StepperMotorSpec, context, meta) {
|
||||
|
||||
private var _position: Int = 0
|
||||
val maxPosition: Int = meta["maxPosition"].int ?: 100
|
||||
|
||||
// Получить текущую позицию мотора
|
||||
suspend fun getPosition(): Int = _position
|
||||
altavir
commented
probably better to use probably better to use `readPosition` instead of `getPosition`. It communicates what the functions do better.
|
||||
|
||||
// Установить позицию мотора
|
||||
suspend fun setPosition(value: Int) {
|
||||
altavir
commented
Consider encapsulating those functions. Consider encapsulating those functions.
|
||||
if (value in 0..maxPosition) {
|
||||
_position = value
|
||||
println("StepperMotorDevice: Перемещен в позицию $_position")
|
||||
delay(100) // Имитация времени на перемещение
|
||||
} else {
|
||||
println("StepperMotorDevice: Неверная позиция $value (макс: $maxPosition)")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = "StepperMotorDevice"
|
||||
}
|
||||
|
||||
// Спецификация клапана
|
||||
object ValveSpec : DeviceSpec<ValveDevice>() {
|
||||
|
||||
val state by booleanProperty(
|
||||
read = { propertyName -> getState() },
|
||||
write = { propertyName, value -> setState(value) }
|
||||
)
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): ValveDevice {
|
||||
return ValveDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация устройства клапана
|
||||
class ValveDevice(
|
||||
altavir
commented
Maybe call it Maybe call it `VirtualValv` or similar to discriminate between virtual devices and real ones.
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<ValveDevice>(ValveSpec, context, meta) {
|
||||
|
||||
private var _state: Boolean = false
|
||||
|
||||
// Получить состояние клапана
|
||||
suspend fun getState(): Boolean = _state
|
||||
|
||||
// Установить состояние клапана
|
||||
suspend fun setState(value: Boolean) {
|
||||
_state = value
|
||||
val stateStr = if (_state) "открыт" else "закрыт"
|
||||
println("ValveDevice: Клапан теперь $stateStr")
|
||||
altavir
commented
Remove debug prints. Use property subscription for testing. Don't use Russian. Remove debug prints. Use property subscription for testing. Don't use Russian.
|
||||
delay(50) // Имитация времени на изменение состояния
|
||||
}
|
||||
|
||||
// Метод для щелчка клапана
|
||||
suspend fun click() {
|
||||
println("ValveDevice: Клик клапана...")
|
||||
setState(true)
|
||||
delay(50)
|
||||
setState(false)
|
||||
println("ValveDevice: Клик клапана завершен")
|
||||
}
|
||||
|
||||
override fun toString(): String = "ValveDevice"
|
||||
}
|
||||
|
||||
// Спецификация камеры давления
|
||||
object PressureChamberSpec : DeviceSpec<PressureChamberDevice>() {
|
||||
|
||||
val pressure by doubleProperty(
|
||||
read = { propertyName -> getPressure() },
|
||||
write = { propertyName, value -> setPressure(value) }
|
||||
)
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): PressureChamberDevice {
|
||||
return PressureChamberDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация устройства камеры давления
|
||||
class PressureChamberDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<PressureChamberDevice>(PressureChamberSpec, context, meta) {
|
||||
|
||||
private var _pressure: Double = 0.0
|
||||
|
||||
// Получить текущее давление
|
||||
suspend fun getPressure(): Double = _pressure
|
||||
altavir
commented
readPressure? readPressure?
|
||||
|
||||
// Установить давление
|
||||
suspend fun setPressure(value: Double) {
|
||||
_pressure = value
|
||||
println("PressureChamberDevice: Давление установлено на $_pressure")
|
||||
delay(50)
|
||||
}
|
||||
|
||||
override fun toString(): String = "PressureChamberDevice"
|
||||
}
|
||||
|
||||
// Спецификация шприцевого насоса
|
||||
object SyringePumpSpec : DeviceSpec<SyringePumpDevice>() {
|
||||
|
||||
val volume by doubleProperty(
|
||||
read = { propertyName -> getVolume() },
|
||||
write = { propertyName, value -> setVolume(value) }
|
||||
)
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): SyringePumpDevice {
|
||||
return SyringePumpDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация устройства шприцевого насоса
|
||||
class SyringePumpDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<SyringePumpDevice>(SyringePumpSpec, context, meta) {
|
||||
|
||||
private var _volume: Double = 0.0
|
||||
val maxVolume: Double = meta["maxVolume"].double ?: 5.0
|
||||
|
||||
// Получить текущий объем
|
||||
suspend fun getVolume(): Double = _volume
|
||||
|
||||
// Установить объем
|
||||
suspend fun setVolume(value: Double) {
|
||||
if (value in 0.0..maxVolume) {
|
||||
_volume = value
|
||||
println("SyringePumpDevice: Объем установлен на $_volume мл")
|
||||
delay(100)
|
||||
} else {
|
||||
println("SyringePumpDevice: Неверный объем $value (макс: $maxVolume)")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = "SyringePumpDevice"
|
||||
}
|
||||
|
||||
// Спецификация датчика реагента
|
||||
object ReagentSensorSpec : DeviceSpec<ReagentSensorDevice>() {
|
||||
|
||||
val isPresent by booleanProperty(
|
||||
read = { propertyName -> checkReagent() }
|
||||
)
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): ReagentSensorDevice {
|
||||
return ReagentSensorDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация устройства датчика реагента
|
||||
class ReagentSensorDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<ReagentSensorDevice>(ReagentSensorSpec, context, meta) {
|
||||
|
||||
// Проверить наличие реагента
|
||||
suspend fun checkReagent(): Boolean {
|
||||
println("ReagentSensorDevice: Проверка наличия реагента...")
|
||||
delay(100) // Имитация времени на проверку
|
||||
val isPresent = true // Предполагаем, что реагент присутствует
|
||||
println("ReagentSensorDevice: Реагент ${if (isPresent) "обнаружен" else "не обнаружен"}")
|
||||
return isPresent
|
||||
}
|
||||
|
||||
override fun toString(): String = "ReagentSensorDevice"
|
||||
}
|
||||
|
||||
// Спецификация иглы
|
||||
object NeedleSpec : DeviceSpec<NeedleDevice>() {
|
||||
|
||||
val mode by enumProperty(
|
||||
enumValues = NeedleDevice.Mode.entries.toTypedArray(),
|
||||
read = { propertyName -> getMode() },
|
||||
write = { propertyName, value -> setMode(value) }
|
||||
)
|
||||
|
||||
val position by doubleProperty(
|
||||
read = { propertyName -> getPosition() },
|
||||
write = { propertyName, value -> setPosition(value) }
|
||||
)
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): NeedleDevice {
|
||||
return NeedleDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация устройства иглы
|
||||
class NeedleDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<NeedleDevice>(NeedleSpec, context, meta) {
|
||||
|
||||
// Режимы работы иглы
|
||||
enum class Mode { SAMPLING, WASHING }
|
||||
|
||||
private var mode: Mode = Mode.WASHING
|
||||
private var position: Double = 0.0 // в мм
|
||||
|
||||
// Получить текущий режим
|
||||
suspend fun getMode(): Mode = mode
|
||||
|
||||
// Установить режим
|
||||
suspend fun setMode(value: Mode) {
|
||||
mode = value
|
||||
println("NeedleDevice: Режим установлен на $mode")
|
||||
delay(50)
|
||||
}
|
||||
|
||||
// Получить текущую позицию
|
||||
suspend fun getPosition(): Double = position
|
||||
|
||||
// Установить позицию
|
||||
suspend fun setPosition(value: Double) {
|
||||
if (value in 0.0..100.0) {
|
||||
position = value
|
||||
println("NeedleDevice: Перемещена в позицию $position мм")
|
||||
delay(100)
|
||||
} else {
|
||||
println("NeedleDevice: Неверная позиция $value мм")
|
||||
}
|
||||
}
|
||||
|
||||
// Выполнить промывку
|
||||
suspend fun performWashing(duration: Int) {
|
||||
println("NeedleDevice: Промывка в течение $duration секунд")
|
||||
delay(duration * 1000L) // Имитация промывки (1 секунда = 1000 мс)
|
||||
}
|
||||
|
||||
// Выполнить забор пробы
|
||||
suspend fun performSampling() {
|
||||
println("NeedleDevice: Забор пробы в позиции $position мм")
|
||||
altavir
commented
Remove debug prints. If needed, use device log Remove debug prints. If needed, use device log
|
||||
delay(500) // Имитация забора пробы
|
||||
}
|
||||
|
||||
override fun toString(): String = "NeedleDevice"
|
||||
}
|
||||
|
||||
// Спецификация шейкера
|
||||
object ShakerSpec : DeviceSpec<ShakerDevice>() {
|
||||
|
||||
val verticalMotor by device(StepperMotorSpec, name = "verticalMotor")
|
||||
val horizontalMotor by device(StepperMotorSpec, name = "horizontalMotor")
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): ShakerDevice {
|
||||
return ShakerDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация устройства шейкера
|
||||
class ShakerDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<ShakerDevice>(ShakerSpec, context, meta) {
|
||||
|
||||
val verticalMotor: StepperMotorDevice
|
||||
get() = nestedDevices["verticalMotor"] as StepperMotorDevice
|
||||
|
||||
val horizontalMotor: StepperMotorDevice
|
||||
get() = nestedDevices["horizontalMotor"] as StepperMotorDevice
|
||||
|
||||
// Метод встряхивания
|
||||
suspend fun shake(cycles: Int) {
|
||||
println("ShakerDevice: Начало встряхивания, циклов: $cycles")
|
||||
repeat(cycles) {
|
||||
verticalMotor.setPosition(3)
|
||||
verticalMotor.setPosition(1)
|
||||
}
|
||||
println("ShakerDevice: Встряхивание завершено")
|
||||
}
|
||||
|
||||
override fun toString(): String = "ShakerDevice"
|
||||
}
|
||||
|
||||
// Спецификация системы транспортировки
|
||||
object TransportationSystemSpec : DeviceSpec<TransportationSystem>() {
|
||||
|
||||
val slideMotor by device(StepperMotorSpec, name = "slideMotor")
|
||||
val pushMotor by device(StepperMotorSpec, name = "pushMotor")
|
||||
val receiveMotor by device(StepperMotorSpec, name = "receiveMotor")
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): TransportationSystem {
|
||||
return TransportationSystem(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация системы транспортировки
|
||||
class TransportationSystem(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<TransportationSystem>(TransportationSystemSpec, context, meta) {
|
||||
|
||||
val slideMotor: StepperMotorDevice
|
||||
get() = nestedDevices["slideMotor"] as StepperMotorDevice
|
||||
|
||||
val pushMotor: StepperMotorDevice
|
||||
get() = nestedDevices["pushMotor"] as StepperMotorDevice
|
||||
|
||||
val receiveMotor: StepperMotorDevice
|
||||
get() = nestedDevices["receiveMotor"] as StepperMotorDevice
|
||||
|
||||
override fun toString(): String = "TransportationSystem"
|
||||
}
|
||||
|
||||
// Спецификация анализатора
|
||||
object AnalyzerSpec : DeviceSpec<AnalyzerDevice>() {
|
||||
|
||||
val transportationSystem by device(TransportationSystemSpec, name = "transportationSystem")
|
||||
val shakerDevice by device(ShakerSpec, name = "shakerDevice")
|
||||
val needleDevice by device(NeedleSpec, name = "needleDevice")
|
||||
|
||||
val valveV20 by device(ValveSpec, name = "valveV20")
|
||||
val valveV17 by device(ValveSpec, name = "valveV17")
|
||||
val valveV18 by device(ValveSpec, name = "valveV18")
|
||||
val valveV35 by device(ValveSpec, name = "valveV35")
|
||||
|
||||
val pressureChamberHigh by device(PressureChamberSpec, name = "pressureChamberHigh")
|
||||
val pressureChamberLow by device(PressureChamberSpec, name = "pressureChamberLow")
|
||||
|
||||
val syringePumpMA100 by device(SyringePumpSpec, name = "syringePumpMA100")
|
||||
val syringePumpMA25 by device(SyringePumpSpec, name = "syringePumpMA25")
|
||||
|
||||
val reagentSensor1 by device(ReagentSensorSpec, name = "reagentSensor1")
|
||||
val reagentSensor2 by device(ReagentSensorSpec, name = "reagentSensor2")
|
||||
val reagentSensor3 by device(ReagentSensorSpec, name = "reagentSensor3")
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): AnalyzerDevice {
|
||||
return AnalyzerDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
// Реализация анализатора
|
||||
class AnalyzerDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<AnalyzerDevice>(AnalyzerSpec, context, meta) {
|
||||
|
||||
val transportationSystem: TransportationSystem
|
||||
get() = nestedDevices["transportationSystem"] as TransportationSystem
|
||||
altavir
commented
Probably better to use DeviceGroup API? Probably better to use DeviceGroup API?
|
||||
|
||||
val shakerDevice: ShakerDevice
|
||||
get() = nestedDevices["shakerDevice"] as ShakerDevice
|
||||
|
||||
val needleDevice: NeedleDevice
|
||||
get() = nestedDevices["needleDevice"] as NeedleDevice
|
||||
|
||||
val valveV20: ValveDevice
|
||||
get() = nestedDevices["valveV20"] as ValveDevice
|
||||
|
||||
val valveV17: ValveDevice
|
||||
get() = nestedDevices["valveV17"] as ValveDevice
|
||||
|
||||
val valveV18: ValveDevice
|
||||
get() = nestedDevices["valveV18"] as ValveDevice
|
||||
|
||||
val valveV35: ValveDevice
|
||||
get() = nestedDevices["valveV35"] as ValveDevice
|
||||
|
||||
val pressureChamberHigh: PressureChamberDevice
|
||||
get() = nestedDevices["pressureChamberHigh"] as PressureChamberDevice
|
||||
|
||||
val pressureChamberLow: PressureChamberDevice
|
||||
get() = nestedDevices["pressureChamberLow"] as PressureChamberDevice
|
||||
|
||||
val syringePumpMA100: SyringePumpDevice
|
||||
get() = nestedDevices["syringePumpMA100"] as SyringePumpDevice
|
||||
|
||||
val syringePumpMA25: SyringePumpDevice
|
||||
get() = nestedDevices["syringePumpMA25"] as SyringePumpDevice
|
||||
|
||||
val reagentSensor1: ReagentSensorDevice
|
||||
get() = nestedDevices["reagentSensor1"] as ReagentSensorDevice
|
||||
|
||||
val reagentSensor2: ReagentSensorDevice
|
||||
get() = nestedDevices["reagentSensor2"] as ReagentSensorDevice
|
||||
|
||||
val reagentSensor3: ReagentSensorDevice
|
||||
get() = nestedDevices["reagentSensor3"] as ReagentSensorDevice
|
||||
|
||||
// Процесс обработки проб
|
||||
suspend fun processSample() {
|
||||
println("Начало процесса забора пробы")
|
||||
|
||||
// Шаг 1: Открыть клапан V20 и начать забор пробы с помощью шприца MA 100 мкл
|
||||
valveV20.setState(true)
|
||||
syringePumpMA100.setVolume(0.1)
|
||||
delay(500) // Имитация времени для забора жидкости
|
||||
valveV20.setState(false)
|
||||
|
||||
// Шаг 2: Открыть клапан V17 и начать подачу лизиса в WBC с помощью MA 2.5 мл
|
||||
valveV17.setState(true)
|
||||
syringePumpMA25.setVolume(2.5)
|
||||
delay(500) // Имитация времени для подачи лизиса
|
||||
valveV17.setState(false)
|
||||
|
||||
// Шаг 3: Очистка системы
|
||||
syringePumpMA100.setVolume(0.0)
|
||||
syringePumpMA25.setVolume(0.0)
|
||||
|
||||
println("Процесс забора пробы завершен")
|
||||
}
|
||||
|
||||
// Реализация рецепта калибровки
|
||||
suspend fun calibrate() {
|
||||
println("Начало калибровки...")
|
||||
|
||||
// Шаг 1: Откалибровать положения всех двигателей
|
||||
val motors = listOf(
|
||||
transportationSystem.slideMotor,
|
||||
transportationSystem.pushMotor,
|
||||
transportationSystem.receiveMotor,
|
||||
shakerDevice.verticalMotor,
|
||||
shakerDevice.horizontalMotor
|
||||
)
|
||||
|
||||
for (motor in motors) {
|
||||
for (position in 0..motor.maxPosition) {
|
||||
motor.setPosition(position)
|
||||
}
|
||||
motor.setPosition(0)
|
||||
}
|
||||
|
||||
// Шаг 2: Щелкнуть всеми клапанами и перевести их в нулевое положение
|
||||
val valves = listOf(valveV20, valveV17, valveV18, valveV35)
|
||||
for (valve in valves) {
|
||||
valve.click()
|
||||
valve.setState(false)
|
||||
}
|
||||
|
||||
// Шаг 3: Провести накачку давления в камеру повышенного давления
|
||||
pressureChamberHigh.setPressure(2.0)
|
||||
|
||||
// Шаг 4: Провести откачку камеры пониженного давления
|
||||
pressureChamberLow.setPressure(-1.0)
|
||||
|
||||
// Шаг 5: Заполнить гидравлическую систему
|
||||
|
||||
// 5.1 Проверить наличие всех реагентов
|
||||
val sensors = listOf(reagentSensor1, reagentSensor2, reagentSensor3)
|
||||
for (sensor in sensors) {
|
||||
sensor.checkReagent()
|
||||
}
|
||||
|
||||
// 5.2 Прокачать все шприцевые дозаторы (5 раз движение между крайними положениями)
|
||||
val pumps = listOf(syringePumpMA100, syringePumpMA25)
|
||||
for (pump in pumps) {
|
||||
repeat(5) {
|
||||
pump.setVolume(pump.maxVolume)
|
||||
pump.setVolume(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
// 5.3 Провести промывку иглы в положении промывки для забора пробы
|
||||
needleDevice.setPosition(0.0)
|
||||
needleDevice.setMode(NeedleDevice.Mode.WASHING)
|
||||
needleDevice.performWashing(5)
|
||||
|
||||
println("Калибровка завершена")
|
||||
}
|
||||
|
||||
// Рецепт 1 - Подача пробирки на измерение
|
||||
suspend fun executeRecipe1() {
|
||||
println("Выполнение Рецепта 1")
|
||||
|
||||
// Шаг 1: Сдвинуть планшет на одну позицию
|
||||
val currentSlidePosition = transportationSystem.slideMotor.getPosition()
|
||||
transportationSystem.slideMotor.setPosition(currentSlidePosition + 1)
|
||||
println("Сдвинуто планшет на позицию ${currentSlidePosition + 1}")
|
||||
|
||||
// Шаг 2: Захват пробирки для смешивания
|
||||
println("Захват пробирки для смешивания")
|
||||
|
||||
// 2.1 - 2.10: Управление шейкером и двигателями
|
||||
shakerDevice.verticalMotor.setPosition(1)
|
||||
shakerDevice.horizontalMotor.setPosition(1)
|
||||
println("Шейкер: вертикальный - 1, горизонтальный - 1")
|
||||
|
||||
shakerDevice.horizontalMotor.setPosition(2)
|
||||
println("Шейкер: горизонтальный - 2")
|
||||
|
||||
shakerDevice.verticalMotor.setPosition(2)
|
||||
println("Шейкер: вертикальный - 2")
|
||||
|
||||
// Встряхивание 5 циклов
|
||||
repeat(5) {
|
||||
shakerDevice.verticalMotor.setPosition(3)
|
||||
shakerDevice.verticalMotor.setPosition(1)
|
||||
println("Шейкер: цикл ${it + 1}")
|
||||
}
|
||||
|
||||
shakerDevice.verticalMotor.setPosition(2)
|
||||
shakerDevice.horizontalMotor.setPosition(1)
|
||||
println("Шейкер: окончание движения")
|
||||
|
||||
// Шаг 3: Забор и измерение пробы
|
||||
executeSampling()
|
||||
needleDevice.setPosition(0.0)
|
||||
println("Игла вернулась в исходное положение")
|
||||
}
|
||||
|
||||
// Функция для выполнения забора пробы
|
||||
suspend fun executeSampling() {
|
||||
println("Забор и измерение пробы")
|
||||
|
||||
needleDevice.setPosition(0.0)
|
||||
needleDevice.setMode(NeedleDevice.Mode.WASHING)
|
||||
needleDevice.performWashing(5)
|
||||
|
||||
needleDevice.setPosition(10.0)
|
||||
needleDevice.setMode(NeedleDevice.Mode.SAMPLING)
|
||||
needleDevice.performSampling()
|
||||
|
||||
needleDevice.setPosition(0.0)
|
||||
needleDevice.setMode(NeedleDevice.Mode.WASHING)
|
||||
needleDevice.performWashing(5)
|
||||
|
||||
needleDevice.setPosition(20.0)
|
||||
println("Игла в положении WOC")
|
||||
}
|
||||
|
||||
// Рецепт 2 - Автоматическое измерение
|
||||
suspend fun executeRecipe2() {
|
||||
println("Выполнение Рецепта 2 - Автоматическое измерение")
|
||||
|
||||
transportationSystem.receiveMotor.setPosition(transportationSystem.receiveMotor.getPosition() + 1)
|
||||
println("Сдвинуто податчик на 1 позицию")
|
||||
|
||||
// Проверка лотка
|
||||
if (!checkTrayInPushSystem()) {
|
||||
println("Лоток отсутствует. Повторный сдвиг")
|
||||
transportationSystem.receiveMotor.setPosition(transportationSystem.receiveMotor.getPosition() + 1)
|
||||
} else {
|
||||
executeSampling()
|
||||
}
|
||||
|
||||
// Если достигнута последняя позиция, меняем планшет
|
||||
if (transportationSystem.receiveMotor.getPosition() >= transportationSystem.receiveMotor.maxPosition) {
|
||||
println("Смена планшета. Возврат податчика в исходное положение")
|
||||
transportationSystem.receiveMotor.setPosition(0)
|
||||
}
|
||||
|
||||
println("Рецепт 2 завершен")
|
||||
needleDevice.setPosition(0.0)
|
||||
println("Игла вернулась в исходное положение")
|
||||
}
|
||||
|
||||
// Рецепт 3 - Одиночное измерение
|
||||
suspend fun executeRecipe3() {
|
||||
println("Выполнение Рецепта 3 - Одиночное измерение")
|
||||
executeSampling()
|
||||
println("Рецепт 3 завершен")
|
||||
needleDevice.setPosition(0.0)
|
||||
println("Игла вернулась в исходное положение")
|
||||
}
|
||||
|
||||
// функция проверки наличия лотка
|
||||
private suspend fun checkTrayInPushSystem(): Boolean {
|
||||
println("Проверка наличия лотка в системе проталкивания")
|
||||
delay(200)
|
||||
return true // Имитация наличия лотка
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// Тестирование анализатора
|
||||
// -----------------------
|
||||
class AnalyzerTest {
|
||||
|
||||
@Test
|
||||
fun testAnalyzerInitialization() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val analyzer = AnalyzerDevice(context)
|
||||
|
||||
analyzer.start()
|
||||
|
||||
// Проверка состояния устройств
|
||||
assertEquals(LifecycleState.STARTED, analyzer.lifecycleState)
|
||||
assertEquals(LifecycleState.STARTED, analyzer.transportationSystem.lifecycleState)
|
||||
assertEquals(LifecycleState.STARTED, analyzer.shakerDevice.lifecycleState)
|
||||
assertEquals(LifecycleState.STARTED, analyzer.needleDevice.lifecycleState)
|
||||
|
||||
// Проверка начальных положений двигателей
|
||||
assertEquals(0, analyzer.transportationSystem.slideMotor.getPosition())
|
||||
assertEquals(0, analyzer.transportationSystem.pushMotor.getPosition())
|
||||
assertEquals(0, analyzer.transportationSystem.receiveMotor.getPosition())
|
||||
assertEquals(0, analyzer.shakerDevice.verticalMotor.getPosition())
|
||||
assertEquals(0, analyzer.shakerDevice.horizontalMotor.getPosition())
|
||||
assertEquals(0.0, analyzer.needleDevice.getPosition())
|
||||
|
||||
// Проверка состояния клапанов
|
||||
assertFalse(analyzer.valveV20.getState())
|
||||
assertFalse(analyzer.valveV17.getState())
|
||||
assertFalse(analyzer.valveV18.getState())
|
||||
assertFalse(analyzer.valveV35.getState())
|
||||
|
||||
analyzer.stop()
|
||||
assertEquals(LifecycleState.STOPPED, analyzer.lifecycleState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCalibration() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val analyzer = AnalyzerDevice(context)
|
||||
|
||||
analyzer.start()
|
||||
|
||||
// Запуск калибровки
|
||||
analyzer.calibrate()
|
||||
|
||||
// Проверка состояния двигателей после калибровки
|
||||
assertEquals(0, analyzer.transportationSystem.slideMotor.getPosition())
|
||||
assertEquals(0, analyzer.transportationSystem.pushMotor.getPosition())
|
||||
assertEquals(0, analyzer.transportationSystem.receiveMotor.getPosition())
|
||||
assertEquals(0, analyzer.shakerDevice.verticalMotor.getPosition())
|
||||
assertEquals(0, analyzer.shakerDevice.horizontalMotor.getPosition())
|
||||
|
||||
// Проверка давления после калибровки
|
||||
assertEquals(2.0, analyzer.pressureChamberHigh.getPressure())
|
||||
assertEquals(-1.0, analyzer.pressureChamberLow.getPressure())
|
||||
|
||||
// Проверка состояния реагентных сенсоров
|
||||
assertTrue(analyzer.reagentSensor1.checkReagent())
|
||||
assertTrue(analyzer.reagentSensor2.checkReagent())
|
||||
assertTrue(analyzer.reagentSensor3.checkReagent())
|
||||
|
||||
analyzer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRecipe1() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val analyzer = AnalyzerDevice(context)
|
||||
|
||||
analyzer.start()
|
||||
|
||||
// Выполнение рецепта 1 (подачи пробирки)
|
||||
analyzer.executeRecipe1()
|
||||
|
||||
// Проверка конечных состояний после рецепта
|
||||
assertEquals(1, analyzer.transportationSystem.slideMotor.getPosition())
|
||||
assertEquals(1, analyzer.shakerDevice.verticalMotor.getPosition())
|
||||
assertEquals(1, analyzer.shakerDevice.horizontalMotor.getPosition())
|
||||
|
||||
// Проверка положения иглы после забора пробы
|
||||
assertEquals(0.0, analyzer.needleDevice.getPosition())
|
||||
|
||||
analyzer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRecipe2() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val analyzer = AnalyzerDevice(context)
|
||||
|
||||
analyzer.start()
|
||||
|
||||
// Выполнение рецепта 2 (автоматическое измерение)
|
||||
analyzer.executeRecipe2()
|
||||
|
||||
// Проверка конечного положения двигателей
|
||||
assertTrue(analyzer.transportationSystem.receiveMotor.getPosition() > 0)
|
||||
|
||||
// Проверка иглы после выполнения рецепта
|
||||
assertEquals(0.0, analyzer.needleDevice.getPosition())
|
||||
|
||||
analyzer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRecipe3() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val analyzer = AnalyzerDevice(context)
|
||||
|
||||
analyzer.start()
|
||||
|
||||
// Выполнение рецепта 3 (одиночное измерение)
|
||||
analyzer.executeRecipe3()
|
||||
|
||||
// Проверка иглы после одиночного измерения
|
||||
assertEquals(0.0, analyzer.needleDevice.getPosition())
|
||||
|
||||
// Проверка завершения одиночного измерения
|
||||
println("Одиночное измерение завершено")
|
||||
|
||||
analyzer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDetailedMotorMovements() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val analyzer = AnalyzerDevice(context)
|
||||
|
||||
analyzer.start()
|
||||
|
||||
// Проверка движения двигателей при выполнении задач
|
||||
analyzer.transportationSystem.slideMotor.setPosition(2)
|
||||
assertEquals(2, analyzer.transportationSystem.slideMotor.getPosition())
|
||||
|
||||
analyzer.shakerDevice.verticalMotor.setPosition(1)
|
||||
analyzer.shakerDevice.horizontalMotor.setPosition(3)
|
||||
assertEquals(1, analyzer.shakerDevice.verticalMotor.getPosition())
|
||||
assertEquals(3, analyzer.shakerDevice.horizontalMotor.getPosition())
|
||||
|
||||
analyzer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testValveStates() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val analyzer = AnalyzerDevice(context)
|
||||
|
||||
analyzer.start()
|
||||
|
||||
// Проверка работы клапанов
|
||||
analyzer.valveV20.setState(true)
|
||||
assertTrue(analyzer.valveV20.getState())
|
||||
|
||||
analyzer.valveV17.setState(false)
|
||||
assertFalse(analyzer.valveV17.getState())
|
||||
|
||||
analyzer.valveV18.click() // Щелчок клапаном
|
||||
assertFalse(analyzer.valveV18.getState())
|
||||
|
||||
analyzer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSyringePumpOperations() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val analyzer = AnalyzerDevice(context)
|
||||
|
||||
analyzer.start()
|
||||
|
||||
// Проверка работы шприцевого насоса
|
||||
analyzer.syringePumpMA100.setVolume(0.05)
|
||||
assertEquals(0.05, analyzer.syringePumpMA100.getVolume())
|
||||
|
||||
analyzer.syringePumpMA100.setVolume(0.0)
|
||||
assertEquals(0.0, analyzer.syringePumpMA100.getVolume())
|
||||
|
||||
analyzer.syringePumpMA25.setVolume(2.5)
|
||||
assertEquals(2.5, analyzer.syringePumpMA25.getVolume())
|
||||
|
||||
analyzer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNeedleOperations() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val analyzer = AnalyzerDevice(context)
|
||||
|
||||
analyzer.start()
|
||||
|
||||
// Проверка работы иглы в режиме промывки и забора проб
|
||||
analyzer.needleDevice.setMode(NeedleDevice.Mode.WASHING)
|
||||
analyzer.needleDevice.setPosition(0.0)
|
||||
assertEquals(0.0, analyzer.needleDevice.getPosition())
|
||||
assertEquals(NeedleDevice.Mode.WASHING, analyzer.needleDevice.getMode())
|
||||
|
||||
analyzer.needleDevice.performWashing(5) // Промывка иглы
|
||||
|
||||
analyzer.needleDevice.setMode(NeedleDevice.Mode.SAMPLING)
|
||||
analyzer.needleDevice.setPosition(10.0)
|
||||
assertEquals(10.0, analyzer.needleDevice.getPosition())
|
||||
assertEquals(NeedleDevice.Mode.SAMPLING, analyzer.needleDevice.getMode())
|
||||
|
||||
analyzer.needleDevice.performSampling() // Забор пробы
|
||||
|
||||
analyzer.stop()
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import space.kscience.controls.api.*
|
||||
import space.kscience.controls.spec.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.*
|
||||
|
||||
class CompositeDeviceTest {
|
||||
|
||||
// simple MotorDevice and MotorSpec
|
||||
class MotorSpec : DeviceSpec<MotorDevice>() {
|
||||
val speed by doubleProperty(
|
||||
name = "speed",
|
||||
read = { propertyName ->
|
||||
getSpeed()
|
||||
},
|
||||
write = { propertyName, value ->
|
||||
setSpeed(value)
|
||||
}
|
||||
)
|
||||
|
||||
val position by doubleProperty(
|
||||
name = "position",
|
||||
read = { propertyName ->
|
||||
getPosition()
|
||||
}
|
||||
// Assuming position is read-only
|
||||
)
|
||||
|
||||
val reset by unitAction(
|
||||
name = "reset",
|
||||
execute = {
|
||||
resetMotor()
|
||||
}
|
||||
)
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): MotorDevice {
|
||||
return MotorDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
class MotorDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<MotorDevice>(MotorSpec(), context, meta) {
|
||||
private var _speed: Double = 0.0
|
||||
private var _position: Double = 0.0
|
||||
|
||||
suspend fun getSpeed(): Double = _speed
|
||||
suspend fun setSpeed(value: Double) {
|
||||
_speed = value
|
||||
}
|
||||
|
||||
suspend fun getPosition(): Double = _position
|
||||
suspend fun resetMotor() {
|
||||
_speed = 0.0
|
||||
_position = 0.0
|
||||
}
|
||||
|
||||
override fun toString(): String = "MotorDevice"
|
||||
|
||||
}
|
||||
|
||||
data class Position(val base: Double, val elbow: Double, val wrist: Double)
|
||||
|
||||
object PositionConverter : MetaConverter<Position> {
|
||||
override fun readOrNull(source: Meta): Position? {
|
||||
val base = source["base"]?.value?.number?.toDouble() ?: return null
|
||||
val elbow = source["elbow"]?.value?.number?.toDouble() ?: return null
|
||||
val wrist = source["wrist"]?.value?.number?.toDouble() ?: return null
|
||||
return Position(base, elbow, wrist)
|
||||
}
|
||||
|
||||
override fun convert(obj: Position): Meta = Meta {
|
||||
"base" put obj.base
|
||||
"elbow" put obj.elbow
|
||||
"wrist" put obj.wrist
|
||||
}
|
||||
}
|
||||
|
||||
object RobotArmSpec : DeviceSpec<RobotArmDevice>() {
|
||||
val baseMotorSpec = MotorSpec()
|
||||
val elbowMotorSpec = MotorSpec()
|
||||
val wristMotorSpec = MotorSpec()
|
||||
|
||||
val baseMotor by device(baseMotorSpec)
|
||||
val elbowMotor by device(elbowMotorSpec)
|
||||
val wristMotor by device(wristMotorSpec)
|
||||
|
||||
val moveToPosition by action(
|
||||
inputConverter = PositionConverter,
|
||||
outputConverter = MetaConverter.unit,
|
||||
name = "moveToPosition",
|
||||
execute = { input ->
|
||||
baseMotor.writeProperty("speed", Meta(input.base))
|
||||
elbowMotor.writeProperty("speed", Meta(input.elbow))
|
||||
wristMotor.writeProperty("speed", Meta(input.wrist))
|
||||
}
|
||||
)
|
||||
|
||||
override fun createDevice(context: Context, meta: Meta): RobotArmDevice {
|
||||
return RobotArmDevice(context, meta)
|
||||
}
|
||||
}
|
||||
|
||||
class RobotArmDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<RobotArmDevice>(RobotArmSpec, context, meta) {
|
||||
|
||||
val baseMotor: MotorDevice get() = nestedDevices["baseMotor"] as MotorDevice
|
||||
val elbowMotor: MotorDevice get() = nestedDevices["elbowMotor"] as MotorDevice
|
||||
val wristMotor: MotorDevice get() = nestedDevices["wristMotor"] as MotorDevice
|
||||
|
||||
override suspend fun onStart() {
|
||||
super.onStart()
|
||||
}
|
||||
|
||||
override suspend fun onStop() {
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun toString(): String = "RobotArmDevice"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNestedDeviceInitialization() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val robotArm = RobotArmDevice(context)
|
||||
|
||||
// Start the robot arm device
|
||||
robotArm.start()
|
||||
|
||||
// Check that all motors are started
|
||||
assertEquals(LifecycleState.STARTED, robotArm.baseMotor.lifecycleState)
|
||||
assertEquals(LifecycleState.STARTED, robotArm.elbowMotor.lifecycleState)
|
||||
assertEquals(LifecycleState.STARTED, robotArm.wristMotor.lifecycleState)
|
||||
|
||||
// Stop the robot arm device
|
||||
robotArm.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompositeDevicePropertyAndActionAccess() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val robotArm = RobotArmDevice(context)
|
||||
|
||||
robotArm.start()
|
||||
|
||||
robotArm.baseMotor.writeProperty("speed", Meta(500.0))
|
||||
|
||||
val speedMeta = robotArm.baseMotor.getProperty("speed")
|
||||
val speed = speedMeta?.value?.number?.toDouble()
|
||||
assertEquals(500.0, speed)
|
||||
|
||||
// Stop the robot arm device
|
||||
robotArm.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompositeDeviceLifecycleManagement() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val robotArm = RobotArmDevice(context)
|
||||
|
||||
// Start the robot arm device
|
||||
robotArm.start()
|
||||
assertEquals(LifecycleState.STARTED, robotArm.lifecycleState)
|
||||
assertEquals(LifecycleState.STARTED, robotArm.baseMotor.lifecycleState)
|
||||
assertEquals(LifecycleState.STARTED, robotArm.elbowMotor.lifecycleState)
|
||||
assertEquals(LifecycleState.STARTED, robotArm.wristMotor.lifecycleState)
|
||||
|
||||
// Stop the robot arm device
|
||||
robotArm.stop()
|
||||
assertEquals(LifecycleState.STOPPED, robotArm.lifecycleState)
|
||||
assertEquals(LifecycleState.STOPPED, robotArm.baseMotor.lifecycleState)
|
||||
assertEquals(LifecycleState.STOPPED, robotArm.elbowMotor.lifecycleState)
|
||||
assertEquals(LifecycleState.STOPPED, robotArm.wristMotor.lifecycleState)
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import space.kscience.controls.api.LifecycleState
|
||||
import space.kscience.controls.spec.DeviceActionSpec
|
||||
import space.kscience.controls.spec.DeviceBase
|
||||
import space.kscience.controls.spec.DeviceBySpec
|
||||
import space.kscience.controls.spec.DevicePropertySpec
|
||||
import space.kscience.controls.spec.DeviceSpec
|
||||
import space.kscience.controls.spec.doubleProperty
|
||||
import space.kscience.controls.spec.unitAction
|
||||
import space.kscience.controls.spec.validate
|
||||
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.number
|
||||
|
||||
class DeviceSpecTest {
|
||||
|
||||
class MotorSpec : DeviceSpec<MotorDevice>() {
|
||||
val speed by doubleProperty(
|
||||
name = "speed",
|
||||
read = { propertyName ->
|
||||
getSpeed()
|
||||
},
|
||||
write = { propertyName, value ->
|
||||
setSpeed(value)
|
||||
}
|
||||
)
|
||||
|
||||
val position by doubleProperty(
|
||||
name = "position",
|
||||
read = { propertyName ->
|
||||
getPosition()
|
||||
}
|
||||
// Assuming position is read-only
|
||||
)
|
||||
|
||||
val reset by unitAction(
|
||||
name = "reset",
|
||||
execute = {
|
||||
resetMotor()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
class MotorDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<MotorDevice>(MotorSpec(), context, meta) {
|
||||
private var _speed: Double = 0.0
|
||||
private var _position: Double = 0.0
|
||||
|
||||
suspend fun getSpeed(): Double = _speed
|
||||
suspend fun setSpeed(value: Double) {
|
||||
_speed = value
|
||||
}
|
||||
|
||||
suspend fun getPosition(): Double = _position
|
||||
suspend fun resetMotor() {
|
||||
_speed = 0.0
|
||||
_position = 0.0
|
||||
}
|
||||
|
||||
override fun toString(): String = "MotorDevice"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDevicePropertyDefinitionAndAccess() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val motor = MotorDevice(context)
|
||||
|
||||
motor.start()
|
||||
|
||||
val speedMeta = Meta(1000.0)
|
||||
motor.writeProperty("speed", speedMeta)
|
||||
|
||||
val speedMetaRead = motor.readProperty("speed")
|
||||
val speed = speedMetaRead.value?.number?.toDouble()
|
||||
assertEquals(1000.0, speed)
|
||||
|
||||
val positionMeta = motor.readProperty("position")
|
||||
val position = positionMeta.value?.number?.toDouble()
|
||||
assertEquals(0.0, position)
|
||||
|
||||
motor.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeviceActionDefinitionAndExecution() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val motor = MotorDevice(context)
|
||||
|
||||
motor.start()
|
||||
|
||||
val speedMeta = Meta(1000.0)
|
||||
motor.writeProperty("speed", speedMeta)
|
||||
|
||||
motor.execute("reset", Meta.EMPTY)
|
||||
|
||||
val speedMetaRead = motor.readProperty("speed")
|
||||
val speed = speedMetaRead.value?.number?.toDouble()
|
||||
assertEquals(0.0, speed)
|
||||
|
||||
val positionMeta = motor.readProperty("position")
|
||||
val position = positionMeta.value?.number?.toDouble()
|
||||
assertEquals(0.0, position)
|
||||
|
||||
motor.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeviceLifecycleManagement() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val motor = MotorDevice(context)
|
||||
|
||||
assertEquals(LifecycleState.STOPPED, motor.lifecycleState)
|
||||
|
||||
motor.start()
|
||||
assertEquals(LifecycleState.STARTED, motor.lifecycleState)
|
||||
|
||||
motor.stop()
|
||||
assertEquals(LifecycleState.STOPPED, motor.lifecycleState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeviceErrorHandling() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val motor = MotorDevice(context)
|
||||
|
||||
motor.start()
|
||||
|
||||
assertFailsWith<IllegalStateException> {
|
||||
motor.readProperty("nonExistentProperty")
|
||||
}
|
||||
|
||||
assertFailsWith<IllegalStateException> {
|
||||
motor.execute("nonExistentAction", Meta.EMPTY)
|
||||
}
|
||||
|
||||
motor.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeviceSpecValidation() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val motor = MotorDevice(context)
|
||||
|
||||
motor.spec.validate(motor)
|
||||
|
||||
val invalidMotor = object : DeviceBase<MotorDevice>(context, Meta.EMPTY) {
|
||||
override val properties: Map<String, DevicePropertySpec<MotorDevice, *>>
|
||||
get() = emptyMap()
|
||||
override val actions: Map<String, DeviceActionSpec<MotorDevice, *, *>>
|
||||
get() = emptyMap()
|
||||
|
||||
override fun toString(): String = "InvalidMotorDevice"
|
||||
}
|
||||
|
||||
// Expect validation to fail
|
||||
assertFailsWith<IllegalStateException> {
|
||||
motor.spec.validate(invalidMotor)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import space.kscience.controls.constructor.dsl.core.EquationSystem
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
import space.kscience.controls.constructor.dsl.core.equations.*
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.*
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
class EquationsTest {
|
||||
|
||||
@Test
|
||||
fun testCreatingEquations() {
|
||||
val xVar = Variable("x", Units.meter)
|
||||
val xExpr = VariableExpression(xVar)
|
||||
val yVar = Variable("y", Units.meter)
|
||||
val yExpr = VariableExpression(yVar)
|
||||
val constExpr = ConstantExpression(5.0, Units.meter)
|
||||
|
||||
// Regular equation: x = y + 5
|
||||
val equation = Equation(xExpr, BinaryExpression(yExpr, BinaryOperator.ADD, constExpr))
|
||||
assertEquals(xExpr, equation.leftExpression)
|
||||
assertEquals(BinaryExpression(yExpr, BinaryOperator.ADD, constExpr), equation.rightExpression)
|
||||
|
||||
// Conditional equation: if x > 0 then y = x else y = -x
|
||||
val condition = BinaryExpression(xExpr, BinaryOperator.GREATER_THAN, ConstantExpression(0.0, Units.meter))
|
||||
val trueEquation = Equation(yExpr, xExpr)
|
||||
val falseEquation = Equation(yExpr, BinaryExpression(ConstantExpression(0.0, Units.meter), BinaryOperator.SUBTRACT, xExpr))
|
||||
val conditionalEquation = ConditionalEquation(condition, trueEquation, falseEquation)
|
||||
assertEquals(condition, conditionalEquation.condition)
|
||||
assertEquals(trueEquation, conditionalEquation.trueEquation)
|
||||
assertEquals(falseEquation, conditionalEquation.falseEquation)
|
||||
|
||||
// Initial equation: x(0) = 0
|
||||
val initialEquation = InitialEquation(xExpr, ConstantExpression(0.0, Units.meter))
|
||||
assertEquals(xExpr, initialEquation.leftExpression)
|
||||
assertEquals(ConstantExpression(0.0, Units.meter), initialEquation.rightExpression)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnitCompatibilityInEquations() {
|
||||
val lengthVar = Variable("length", Units.meter)
|
||||
val lengthExpr = VariableExpression(lengthVar)
|
||||
val timeVar = Variable("time", Units.second)
|
||||
val timeExpr = VariableExpression(timeVar)
|
||||
|
||||
// Correct equation: length = length + length
|
||||
val validEquation = Equation(lengthExpr, BinaryExpression(lengthExpr, BinaryOperator.ADD, ConstantExpression(5.0, Units.meter)))
|
||||
assertFailsWith<Exception> {
|
||||
validEquation
|
||||
}
|
||||
|
||||
// Incorrect equation: length = time
|
||||
val exception = assertFailsWith<IllegalArgumentException> {
|
||||
Equation(lengthExpr, timeExpr)
|
||||
}
|
||||
assertEquals("Units of left and right expressions in the equation are not compatible.", exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEquationSystem() {
|
||||
val xVar = Variable("x", Units.meter)
|
||||
val xExpr = VariableExpression(xVar)
|
||||
val vVar = Variable("v", Units.meter / Units.second)
|
||||
val vExpr = VariableExpression(vVar)
|
||||
val aVar = Variable("a", Units.meter / Units.second.pow(2.0))
|
||||
val aExpr = VariableExpression(aVar)
|
||||
|
||||
// Differential equation: der(x) = v
|
||||
val derX = DerivativeExpression(xExpr)
|
||||
val eq1 = Equation(derX, vExpr)
|
||||
|
||||
// Algebraic equation: v = a * t
|
||||
val tVar = Variable("t", Units.second)
|
||||
val tExpr = VariableExpression(tVar)
|
||||
val eq2 = Equation(vExpr, BinaryExpression(aExpr, BinaryOperator.MULTIPLY, tExpr))
|
||||
|
||||
val equationSystem = EquationSystem()
|
||||
equationSystem.addEquation(eq1)
|
||||
equationSystem.addEquation(eq2)
|
||||
|
||||
assertEquals(2, equationSystem.equations.size)
|
||||
assertTrue(equationSystem.variables.containsAll(listOf("x", "v", "a", "t")))
|
||||
assertTrue(equationSystem.derivativeVariables.contains("x"))
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import space.kscience.controls.constructor.dsl.core.events.Event
|
||||
import space.kscience.controls.constructor.dsl.core.events.ReinitAction
|
||||
import space.kscience.controls.constructor.dsl.core.events.AssignAction
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.BinaryExpression
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.BinaryOperator
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.ConstantExpression
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.VariableExpression
|
||||
import space.kscience.controls.constructor.dsl.core.simulation.SimulationContext
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
||||
class EventsAndActionsTest {
|
||||
|
||||
@Test
|
||||
fun testCreatingEvents() {
|
||||
val xVar = Variable("x", Units.meter)
|
||||
val xExpr = VariableExpression(xVar)
|
||||
|
||||
val condition = BinaryExpression(xExpr, BinaryOperator.GREATER_THAN, ConstantExpression(10.0, Units.meter))
|
||||
val action = AssignAction(xVar, ConstantExpression(0.0, Units.meter))
|
||||
|
||||
val event = Event(condition, listOf(action))
|
||||
|
||||
assertEquals(condition, event.condition)
|
||||
assertEquals(1, event.actions.size)
|
||||
assertEquals(action, event.actions[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testProcessingEventsInSimulation() = runTest {
|
||||
val xVar = Variable("x", Units.meter)
|
||||
val xExpr = VariableExpression(xVar)
|
||||
val context = SimulationContext(CoroutineScope(Dispatchers.Default)).apply {
|
||||
registerVariable(xVar) // Регистрация переменной
|
||||
launch { updateVariable("x", 5.0) } // Установка начального значения переменной
|
||||
}
|
||||
|
||||
val condition = BinaryExpression(xExpr, BinaryOperator.GREATER_THAN, ConstantExpression(10.0, Units.meter))
|
||||
val action = AssignAction(xVar, ConstantExpression(0.0, Units.meter))
|
||||
val event = Event(condition, listOf(action))
|
||||
|
||||
context.events.add(event)
|
||||
|
||||
// Simulate x crossing the threshold
|
||||
context.updateVariable("x", 15.0)
|
||||
context.handleEvents()
|
||||
|
||||
assertEquals(0.0, context.getCurrentVariableValue("x"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReinitAction() = runTest {
|
||||
val vVar = Variable("v", Units.meter / Units.second)
|
||||
val vExpr = VariableExpression(vVar)
|
||||
val context = SimulationContext(CoroutineScope(Dispatchers.Default)).apply {
|
||||
registerVariable(vVar) // Регистрация переменной
|
||||
launch { updateVariable("v", 20.0) } // Установка начального значения переменной
|
||||
}
|
||||
|
||||
val action = ReinitAction(vVar, ConstantExpression(0.0, Units.meter / Units.second))
|
||||
action.execute(context)
|
||||
|
||||
assertEquals(0.0, context.getCurrentVariableValue("v"))
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.*
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
class ExpressionsTest {
|
||||
|
||||
@Test
|
||||
fun testCreationOfDifferentTypesOfExpressions() {
|
||||
// Constant expression
|
||||
val constExpr = ConstantExpression(5.0, Units.meter)
|
||||
assertEquals(5.0, constExpr.value)
|
||||
assertEquals(Units.meter, constExpr.unit)
|
||||
|
||||
// Variable expression
|
||||
val speedVar = Variable("speed", Units.meter / Units.second)
|
||||
val speedExpr = VariableExpression(speedVar)
|
||||
assertEquals(speedVar, speedExpr.variable)
|
||||
assertEquals(Units.meter / Units.second, speedExpr.unit)
|
||||
|
||||
// Binary expression (addition)
|
||||
val distanceVar = Variable("distance", Units.meter)
|
||||
val distanceExpr = VariableExpression(distanceVar)
|
||||
val totalDistanceExpr = BinaryExpression(distanceExpr, BinaryOperator.ADD, constExpr)
|
||||
assertEquals(Units.meter, totalDistanceExpr.unit)
|
||||
|
||||
// Function call expression
|
||||
val sinExpr = FunctionCallExpression("sin", listOf(constExpr))
|
||||
assertEquals(Units.dimensionless, sinExpr.unit)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEvaluationOfExpressions() {
|
||||
val xVar = Variable("x", Units.meter)
|
||||
val xExpr = VariableExpression(xVar)
|
||||
val constExpr = ConstantExpression(2.0, Units.meter)
|
||||
|
||||
val values = mapOf("x" to 3.0)
|
||||
|
||||
// Binary expression: x + 2
|
||||
val sumExpr = BinaryExpression(xExpr, BinaryOperator.ADD, constExpr)
|
||||
assertEquals(5.0, sumExpr.evaluate(values))
|
||||
|
||||
// Binary expression: x * 2
|
||||
val mulExpr = BinaryExpression(xExpr, BinaryOperator.MULTIPLY, ConstantExpression(2.0, Units.dimensionless))
|
||||
assertEquals(6.0, mulExpr.evaluate(values))
|
||||
|
||||
// Function call: sin(x)
|
||||
val sinExpr = FunctionCallExpression("sin", listOf(xExpr))
|
||||
assertEquals(kotlin.math.sin(3.0), sinExpr.evaluate(values))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDimensionCheckingInExpressions() {
|
||||
val lengthVar = Variable("length", Units.meter)
|
||||
val timeVar = Variable("time", Units.second)
|
||||
val lengthExpr = VariableExpression(lengthVar)
|
||||
val timeExpr = VariableExpression(timeVar)
|
||||
|
||||
// Correct addition
|
||||
val sumExpr = BinaryExpression(lengthExpr, BinaryOperator.ADD, ConstantExpression(5.0, Units.meter))
|
||||
assertFailsWith<Exception> {
|
||||
sumExpr.checkDimensions()
|
||||
}
|
||||
|
||||
// Incorrect addition (length + time)
|
||||
val invalidSumExpr = BinaryExpression(lengthExpr, BinaryOperator.ADD, timeExpr)
|
||||
val exception = assertFailsWith<IllegalArgumentException> {
|
||||
invalidSumExpr.checkDimensions()
|
||||
}
|
||||
assertEquals("Units must be the same for addition or subtraction.", exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDerivativeOfExpressions() {
|
||||
val xVar = Variable("x", Units.meter)
|
||||
val xExpr = VariableExpression(xVar)
|
||||
val constExpr = ConstantExpression(5.0, Units.meter)
|
||||
|
||||
// Derivative of constant: should be zero
|
||||
val derivativeConst = constExpr.derivative(xExpr)
|
||||
assertTrue(derivativeConst is ConstantExpression)
|
||||
assertEquals(0.0, derivativeConst.value)
|
||||
|
||||
// Derivative of variable with respect to itself: should be one
|
||||
val derivativeVar = xExpr.derivative(xExpr)
|
||||
assertTrue(derivativeVar is ConstantExpression)
|
||||
assertEquals(1.0, derivativeVar.value)
|
||||
|
||||
// Derivative of x + 5 with respect to x: should be one
|
||||
val sumExpr = BinaryExpression(xExpr, BinaryOperator.ADD, constExpr)
|
||||
val derivativeSum = sumExpr.derivative(xExpr)
|
||||
assertTrue(derivativeSum is ConstantExpression)
|
||||
assertEquals(1.0, derivativeSum.value)
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import space.kscience.controls.constructor.dsl.core.functions.FunctionBuilder
|
||||
import space.kscience.controls.constructor.dsl.core.expressions.*
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
|
||||
class FunctionsTest {
|
||||
|
||||
@Test
|
||||
fun testCreatingFunctions() {
|
||||
val functionBuilder = FunctionBuilder("add").apply {
|
||||
val aVar = input("a", Units.dimensionless)
|
||||
val bVar = input("b", Units.dimensionless)
|
||||
val resultVar = output("result", Units.dimensionless)
|
||||
|
||||
algorithm {
|
||||
assign(resultVar, BinaryExpression(VariableExpression(aVar), BinaryOperator.ADD, VariableExpression(bVar)))
|
||||
}
|
||||
}
|
||||
|
||||
val addFunction = functionBuilder.build()
|
||||
assertEquals("add", addFunction.name)
|
||||
assertEquals(2, addFunction.inputs.size)
|
||||
assertEquals(1, addFunction.outputs.size)
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import space.kscience.controls.constructor.CompositeDeviceTest.Position
|
||||
import space.kscience.controls.constructor.CompositeDeviceTest.PositionConverter
|
||||
import space.kscience.controls.constructor.CompositeDeviceTest.RobotArmDevice
|
||||
import space.kscience.controls.spec.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.number
|
||||
|
||||
class IntegrationTest {
|
||||
|
||||
@Test
|
||||
fun testFullDeviceSimulation() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val robotArm = RobotArmDevice(context)
|
||||
|
||||
robotArm.start()
|
||||
|
||||
val position = Position(base = 90.0, elbow = 45.0, wrist = 30.0)
|
||||
robotArm.execute("moveToPosition", PositionConverter.convert(position))
|
||||
|
||||
val baseSpeedMeta = robotArm.baseMotor.readProperty("speed")
|
||||
val baseSpeed = baseSpeedMeta.value?.number?.toDouble()
|
||||
assertEquals(90.0, baseSpeed)
|
||||
|
||||
val elbowSpeedMeta = robotArm.elbowMotor.readProperty("speed")
|
||||
val elbowSpeed = elbowSpeedMeta.value?.number?.toDouble()
|
||||
assertEquals(45.0, elbowSpeed)
|
||||
|
||||
val wristSpeedMeta = robotArm.wristMotor.readProperty("speed")
|
||||
val wristSpeed = wristSpeedMeta.value?.number?.toDouble()
|
||||
assertEquals(30.0, wristSpeed)
|
||||
|
||||
robotArm.stop()
|
||||
}
|
||||
|
||||
class SensorDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<SensorDevice>(SensorSpec, context, meta) {
|
||||
private var _pressure: Double = 101325.0 // Atmospheric pressure in Pascals
|
||||
|
||||
suspend fun getPressure(): Double = _pressure
|
||||
|
||||
override fun toString(): String = "SensorDevice"
|
||||
|
||||
}
|
||||
|
||||
object SensorSpec : DeviceSpec<SensorDevice>() {
|
||||
val pressure by doubleProperty(
|
||||
name = "pressure",
|
||||
read = { propertyName ->
|
||||
getPressure()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnitsInDeviceProperties() = runTest {
|
||||
val context = Context("TestContext")
|
||||
|
||||
val sensor = SensorDevice(context)
|
||||
|
||||
sensor.start()
|
||||
|
||||
val pressureMeta = sensor.readProperty("pressure")
|
||||
val pressureValue = pressureMeta.value?.number?.toDouble()
|
||||
assertEquals(101325.0, pressureValue)
|
||||
|
||||
sensor.stop()
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import space.kscience.controls.constructor.units.Degrees
|
||||
import space.kscience.controls.constructor.units.Meters
|
||||
import space.kscience.controls.constructor.units.NumericalValue
|
||||
import space.kscience.controls.constructor.units.Radians
|
||||
import space.kscience.controls.constructor.units.SIPrefix
|
||||
import space.kscience.controls.constructor.units.Seconds
|
||||
import space.kscience.controls.constructor.units.UnitsOfMeasurement
|
||||
import space.kscience.controls.constructor.units.withPrefix
|
||||
import kotlin.math.PI
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class UnitsOfMeasurementTest {
|
||||
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import space.kscience.controls.constructor.dsl.core.units.Dimension
|
||||
import space.kscience.controls.constructor.dsl.core.units.PhysicalUnit
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
|
||||
class UnitsTest {
|
||||
|
||||
@Test
|
||||
fun testCreationOfBaseUnits() {
|
||||
val meter = Units.meter
|
||||
assertEquals("meter", meter.name)
|
||||
assertEquals("m", meter.symbol)
|
||||
assertEquals(1.0, meter.conversionFactorToSI)
|
||||
assertEquals(Dimension(length = 1.0), meter.dimension)
|
||||
|
||||
val kilogram = Units.kilogram
|
||||
assertEquals("kilogram", kilogram.name)
|
||||
assertEquals("kg", kilogram.symbol)
|
||||
assertEquals(1.0, kilogram.conversionFactorToSI)
|
||||
assertEquals(Dimension(mass = 1.0), kilogram.dimension)
|
||||
|
||||
val second = Units.second
|
||||
assertEquals("second", second.name)
|
||||
assertEquals("s", second.symbol)
|
||||
assertEquals(1.0, second.conversionFactorToSI)
|
||||
assertEquals(Dimension(time = 1.0), second.dimension)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArithmeticOperationsOnUnits() {
|
||||
val meter = Units.meter
|
||||
val second = Units.second
|
||||
val kilogram = Units.kilogram
|
||||
|
||||
val velocityUnit = meter / second
|
||||
assertEquals(Dimension(length = 1.0, time = -1.0), velocityUnit.dimension)
|
||||
assertEquals("m/s", velocityUnit.symbol)
|
||||
|
||||
val accelerationUnit = meter / (second * second)
|
||||
assertEquals(Dimension(length = 1.0, time = -2.0), accelerationUnit.dimension)
|
||||
assertEquals("m/s^2", accelerationUnit.symbol)
|
||||
|
||||
val forceUnit = kilogram * accelerationUnit
|
||||
assertEquals(Dimension(mass = 1.0, length = 1.0, time = -2.0), forceUnit.dimension)
|
||||
assertEquals("kg*m/s^2", forceUnit.symbol)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnitCompatibilityForAdditionAndSubtraction() {
|
||||
val meter = Units.meter
|
||||
val kilogram = Units.kilogram
|
||||
|
||||
// Correct addition
|
||||
val length1 = meter
|
||||
val length2 = meter
|
||||
assertEquals(length1.dimension, length2.dimension)
|
||||
|
||||
// Incorrect addition should throw exception
|
||||
val mass = kilogram
|
||||
val exception = assertFailsWith<IllegalArgumentException> {
|
||||
if (length1.dimension != mass.dimension) {
|
||||
throw IllegalArgumentException("Units are not compatible for addition.")
|
||||
}
|
||||
}
|
||||
assertEquals("Units are not compatible for addition.", exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnitConversion() {
|
||||
val kilometer = PhysicalUnit("kilometer", "km", 1000.0, Units.meter.dimension)
|
||||
val centimeter = PhysicalUnit("centimeter", "cm", 0.01, Units.meter.dimension)
|
||||
|
||||
// Convert 1 km to meters
|
||||
val kmInMeters = 1.0 * kilometer.conversionFactorToSI
|
||||
assertEquals(1000.0, kmInMeters)
|
||||
|
||||
// Convert 100 cm to meters
|
||||
val cmInMeters = 100.0 * centimeter.conversionFactorToSI
|
||||
assertEquals(1.0, cmInMeters)
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import space.kscience.controls.spec.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.boolean
|
||||
import space.kscience.dataforge.meta.number
|
||||
|
||||
|
||||
class UtilitiesTest {
|
||||
|
||||
class SensorSpec : DeviceSpec<SensorDevice>() {
|
||||
val temperature by doubleProperty(
|
||||
name = "temperature",
|
||||
read = { propertyName ->
|
||||
getTemperature()
|
||||
},
|
||||
write = { propertyName, value ->
|
||||
setTemperature(value)
|
||||
}
|
||||
)
|
||||
|
||||
val isActive by booleanProperty(
|
||||
name = "isActive",
|
||||
read = { propertyName ->
|
||||
isActive()
|
||||
},
|
||||
write = { propertyName, value ->
|
||||
setActive(value)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
class SensorDevice(
|
||||
context: Context,
|
||||
meta: Meta = Meta.EMPTY
|
||||
) : DeviceBySpec<SensorDevice>(SensorSpec(), context, meta) {
|
||||
private var _temperature: Double = 25.0
|
||||
private var _isActive: Boolean = true
|
||||
|
||||
suspend fun getTemperature(): Double = _temperature
|
||||
suspend fun setTemperature(value: Double) {
|
||||
_temperature = value
|
||||
}
|
||||
|
||||
suspend fun isActive(): Boolean = _isActive
|
||||
suspend fun setActive(value: Boolean) {
|
||||
_isActive = value
|
||||
}
|
||||
|
||||
override fun toString(): String = "SensorDevice"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDoublePropertyUtility() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val sensor = SensorDevice(context)
|
||||
|
||||
sensor.start()
|
||||
|
||||
sensor.writeProperty("temperature", Meta(30.0))
|
||||
|
||||
val tempMeta = sensor.readProperty("temperature")
|
||||
val temperature = tempMeta.value?.number?.toDouble()
|
||||
assertEquals(30.0, temperature)
|
||||
|
||||
sensor.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBooleanPropertyUtility() = runTest {
|
||||
val context = Context("TestContext")
|
||||
val sensor = SensorDevice(context)
|
||||
|
||||
sensor.start()
|
||||
|
||||
sensor.writeProperty("isActive", Meta(false))
|
||||
|
||||
val activeMeta = sensor.readProperty("isActive")
|
||||
val isActive = activeMeta.value?.boolean
|
||||
assertEquals(false, isActive)
|
||||
|
||||
sensor.stop()
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package space.kscience.controls.constructor
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNull
|
||||
import space.kscience.controls.constructor.dsl.core.units.Units
|
||||
import space.kscience.controls.constructor.dsl.core.variables.ParameterVariable
|
||||
import space.kscience.controls.constructor.dsl.core.variables.Variable
|
||||
|
||||
class VariablesTest {
|
||||
|
||||
@Test
|
||||
fun testCreationOfVariablesAndParameters() {
|
||||
val length = Variable("length", Units.meter)
|
||||
assertEquals("length", length.name)
|
||||
assertEquals(Units.meter, length.unit)
|
||||
assertNull(length.value)
|
||||
|
||||
val mass = ParameterVariable("mass", Units.kilogram, value = 10.0)
|
||||
assertEquals("mass", mass.name)
|
||||
assertEquals(Units.kilogram, mass.unit)
|
||||
assertEquals(10.0, mass.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSettingAndGettingValuesOfVariablesAndParameters() {
|
||||
val speed = Variable("speed", Units.meter / Units.second)
|
||||
speed.value = 15.0
|
||||
assertEquals(15.0, speed.value)
|
||||
|
||||
val density = ParameterVariable("density", Units.kilogram / Units.meter.pow(3.0), value = 1000.0)
|
||||
assertEquals(1000.0, density.value)
|
||||
|
||||
// Attempt to create a parameter with value outside the allowed range
|
||||
val exception = assertFailsWith<IllegalArgumentException> {
|
||||
ParameterVariable("temperature", Units.kelvin, value = -10.0, min = 0.0)
|
||||
}
|
||||
assertEquals("Value of parameter temperature is less than minimum allowed value.", exception.message)
|
||||
}
|
||||
}
|
@ -50,6 +50,13 @@ public abstract class DeviceBase<D : Device>(
|
||||
final override val meta: Meta = Meta.EMPTY,
|
||||
) : CachingDevice {
|
||||
|
||||
private val stateLock = Mutex()
|
||||
|
||||
/**
|
||||
* Logical state store
|
||||
*/
|
||||
private val logicalState: MutableMap<String, Meta?> = HashMap()
|
||||
|
||||
/**
|
||||
* Collection of property specifications
|
||||
*/
|
||||
@ -89,20 +96,12 @@ public abstract class DeviceBase<D : Device>(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Logical state store
|
||||
*/
|
||||
private val logicalState: HashMap<String, Meta?> = HashMap()
|
||||
|
||||
public override val messageFlow: SharedFlow<DeviceMessage> get() = sharedMessageFlow
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal val self: D
|
||||
get() = this as D
|
||||
|
||||
private val stateLock = Mutex()
|
||||
|
||||
/**
|
||||
* Update logical property state and notify listeners
|
||||
*/
|
||||
|
@ -2,7 +2,7 @@ package space.kscience.controls.spec
|
||||
|
||||
import space.kscience.controls.api.Device
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.*
|
||||
|
||||
/**
|
||||
* A device generated from specification
|
||||
@ -16,14 +16,36 @@ public open class DeviceBySpec<D : Device>(
|
||||
override val properties: Map<String, DevicePropertySpec<D, *>> get() = spec.properties
|
||||
override val actions: Map<String, DeviceActionSpec<D, *, *>> get() = spec.actions
|
||||
|
||||
// Map to store instances of nested devices
|
||||
private val _nestedDevices = hashMapOf<String, Device>()
|
||||
|
||||
// Provide access to nested devices
|
||||
public val nestedDevices: Map<String, Device> get() = _nestedDevices
|
||||
|
||||
override suspend fun onStart(): Unit = with(spec) {
|
||||
for ((name, deviceSpec) in spec.nestedDeviceSpecs) {
|
||||
val nestedDevice = createNestedDevice(deviceSpec, name)
|
||||
_nestedDevices[name] = nestedDevice
|
||||
nestedDevice.start()
|
||||
}
|
||||
self.onOpen()
|
||||
}
|
||||
|
||||
override suspend fun onStop(): Unit = with(spec){
|
||||
for (device in _nestedDevices.values) {
|
||||
device.stop()
|
||||
}
|
||||
self.onClose()
|
||||
}
|
||||
|
||||
|
||||
override fun toString(): String = "Device(spec=$spec)"
|
||||
|
||||
private fun <ND : Device> createNestedDevice(deviceSpec: DeviceSpec<ND>, name: String): ND {
|
||||
// Create an instance of the nested device.
|
||||
val nestedMeta = meta[name] ?: Meta.EMPTY
|
||||
val nestedDevice = deviceSpec.createDevice(context, nestedMeta)
|
||||
return nestedDevice
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package space.kscience.controls.spec
|
||||
|
||||
import kotlinx.coroutines.withContext
|
||||
import space.kscience.controls.api.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaConverter
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
@ -20,15 +21,34 @@ public val MetaConverter.Companion.unit: MetaConverter<Unit> get() = UnitMetaCon
|
||||
|
||||
@OptIn(InternalDeviceAPI::class)
|
||||
public abstract class DeviceSpec<D : Device> {
|
||||
//initializing the metadata property for everyone
|
||||
private val _properties = hashMapOf<String, DevicePropertySpec<D, *>>(
|
||||
DeviceMetaPropertySpec.name to DeviceMetaPropertySpec
|
||||
)
|
||||
// Map to store properties
|
||||
private val _properties = hashMapOf<String, DevicePropertySpec<D, *>>()
|
||||
public val properties: Map<String, DevicePropertySpec<D, *>> get() = _properties
|
||||
|
||||
private val _actions = HashMap<String, DeviceActionSpec<D, *, *>>()
|
||||
// Map to store actions
|
||||
private val _actions = hashMapOf<String, DeviceActionSpec<D, *, *>>()
|
||||
public val actions: Map<String, DeviceActionSpec<D, *, *>> get() = _actions
|
||||
|
||||
/**
|
||||
* Registers a property in the spec.
|
||||
*/
|
||||
public fun <T, P : DevicePropertySpec<D, T>> registerProperty(deviceProperty: P): P {
|
||||
_properties[deviceProperty.name] = deviceProperty
|
||||
return deviceProperty
|
||||
}
|
||||
|
||||
// Map to store nested device specifications
|
||||
private val _nestedDeviceSpecs = hashMapOf<String, DeviceSpec<*>>()
|
||||
public val nestedDeviceSpecs: Map<String, DeviceSpec<*>> get() = _nestedDeviceSpecs
|
||||
|
||||
|
||||
/**
|
||||
* Registers an action in the spec.
|
||||
*/
|
||||
public fun <I, O> registerAction(deviceAction: DeviceActionSpec<D, I, O>): DeviceActionSpec<D, I, O> {
|
||||
_actions[deviceAction.name] = deviceAction
|
||||
return deviceAction
|
||||
}
|
||||
|
||||
public open suspend fun D.onOpen() {
|
||||
}
|
||||
@ -36,12 +56,6 @@ public abstract class DeviceSpec<D : Device> {
|
||||
public open suspend fun D.onClose() {
|
||||
}
|
||||
|
||||
|
||||
public fun <T, P : DevicePropertySpec<D, T>> registerProperty(deviceProperty: P): P {
|
||||
_properties[deviceProperty.name] = deviceProperty
|
||||
return deviceProperty
|
||||
}
|
||||
|
||||
public fun <T> property(
|
||||
converter: MetaConverter<T>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
@ -110,12 +124,6 @@ public abstract class DeviceSpec<D : Device> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public fun <I, O> registerAction(deviceAction: DeviceActionSpec<D, I, O>): DeviceActionSpec<D, I, O> {
|
||||
_actions[deviceAction.name] = deviceAction
|
||||
return deviceAction
|
||||
}
|
||||
|
||||
public fun <I, O> action(
|
||||
inputConverter: MetaConverter<I>,
|
||||
outputConverter: MetaConverter<O>,
|
||||
@ -156,6 +164,24 @@ public abstract class DeviceSpec<D : Device> {
|
||||
deviceAction
|
||||
}
|
||||
}
|
||||
|
||||
public open fun createDevice(context: Context, meta: Meta): D {
|
||||
// Since DeviceSpec<D> doesn't know how to create an instance of D,
|
||||
// you need to override this method in subclasses.
|
||||
throw NotImplementedError("createDevice must be implemented in subclasses")
|
||||
}
|
||||
|
||||
public fun <ND : Device> device(
|
||||
deviceSpec: DeviceSpec<ND>,
|
||||
name: String? = null
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DeviceSpec<ND>>> =
|
||||
PropertyDelegateProvider { _, property ->
|
||||
val deviceName = name ?: property.name
|
||||
// Register the nested device spec
|
||||
_nestedDeviceSpecs[deviceName] = deviceSpec
|
||||
ReadOnlyProperty { _, _ -> deviceSpec }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,6 +6,8 @@ import space.kscience.controls.api.metaDescriptor
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaConverter
|
||||
import space.kscience.dataforge.meta.ValueType
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.string
|
||||
import kotlin.properties.PropertyDelegateProvider
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
@ -192,3 +194,122 @@ public fun <D : Device> DeviceSpec<D>.metaProperty(
|
||||
write: suspend D.(propertyName: String, value: Meta) -> Unit
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, MutableDevicePropertySpec<D, Meta>>> =
|
||||
mutableProperty(MetaConverter.meta, descriptorBuilder, name, read, write)
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.doubleProperty(
|
||||
description: String? = null,
|
||||
name: String? = null,
|
||||
read: suspend D.(String) -> Double?,
|
||||
write: (suspend D.(String, Double) -> Unit)? = null
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Double>>> {
|
||||
val converter = MetaConverter.double
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {
|
||||
this.description = description
|
||||
metaDescriptor {
|
||||
valueType(ValueType.NUMBER)
|
||||
}
|
||||
}
|
||||
return if (write != null) {
|
||||
mutableProperty(converter, descriptorBuilder, name, read, write)
|
||||
} else {
|
||||
property(converter, descriptorBuilder, name, read)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.booleanProperty(
|
||||
description: String? = null,
|
||||
name: String? = null,
|
||||
read: suspend D.(String) -> Boolean?,
|
||||
write: (suspend D.(String, Boolean) -> Unit)? = null
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Boolean>>> {
|
||||
val converter = MetaConverter.boolean
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {
|
||||
this.description = description
|
||||
metaDescriptor {
|
||||
valueType(ValueType.BOOLEAN)
|
||||
}
|
||||
}
|
||||
return if (write != null) {
|
||||
mutableProperty(converter, descriptorBuilder, name, read, write)
|
||||
} else {
|
||||
property(converter, descriptorBuilder, name, read)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.stringProperty(
|
||||
description: String? = null,
|
||||
name: String? = null,
|
||||
read: suspend D.(String) -> String?,
|
||||
write: (suspend D.(String, String) -> Unit)? = null
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, String>>> {
|
||||
val converter = MetaConverter.string
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {
|
||||
this.description = description
|
||||
metaDescriptor {
|
||||
valueType(ValueType.STRING)
|
||||
}
|
||||
}
|
||||
return if (write != null) {
|
||||
mutableProperty(converter, descriptorBuilder, name, read, write)
|
||||
} else {
|
||||
property(converter, descriptorBuilder, name, read)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <D : Device> DeviceSpec<D>.intProperty(
|
||||
description: String? = null,
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> Int?,
|
||||
write: (suspend D.(propertyName: String, value: Int) -> Unit)? = null
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, Int>>> {
|
||||
val converter = MetaConverter.int
|
||||
val descriptorBuilder: PropertyDescriptor.() -> Unit = {
|
||||
this.description = description
|
||||
metaDescriptor {
|
||||
valueType(ValueType.NUMBER)
|
||||
}
|
||||
}
|
||||
return if (write != null) {
|
||||
mutableProperty(converter, descriptorBuilder, name, read, write)
|
||||
} else {
|
||||
property(converter, descriptorBuilder, name, read)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <E : Enum<E>, D : Device> DeviceSpec<D>.enumProperty(
|
||||
enumValues: Array<E>,
|
||||
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
|
||||
name: String? = null,
|
||||
read: suspend D.(propertyName: String) -> E?,
|
||||
write: (suspend D.(propertyName: String, value: E) -> Unit)? = null
|
||||
): PropertyDelegateProvider<DeviceSpec<D>, ReadOnlyProperty<DeviceSpec<D>, DevicePropertySpec<D, E>>> {
|
||||
val converter = object : MetaConverter<E> {
|
||||
override val descriptor: MetaDescriptor = MetaDescriptor {
|
||||
valueType(ValueType.STRING)
|
||||
allowedValues(enumValues.map { it.name })
|
||||
}
|
||||
|
||||
override fun readOrNull(source: Meta): E? {
|
||||
val value = source.string ?: return null
|
||||
return enumValues.firstOrNull { it.name == value }
|
||||
}
|
||||
|
||||
override fun convert(obj: E): Meta = Meta(obj.name)
|
||||
}
|
||||
|
||||
return if (write != null) {
|
||||
mutableProperty(
|
||||
converter = converter,
|
||||
descriptorBuilder = descriptorBuilder,
|
||||
name = name,
|
||||
read = read,
|
||||
write = write
|
||||
)
|
||||
} else {
|
||||
property(
|
||||
converter = converter,
|
||||
descriptorBuilder = descriptorBuilder,
|
||||
name = name,
|
||||
read = read
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user
What is the reason for this method in the state container? Why
computeValue
does not take parameters?