Some changes for testing
This commit is contained in:
parent
3f09d20465
commit
b6f7963f68
@ -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,
|
||||
): 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
|
||||
): 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(
|
||||
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 {
|
||||
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
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
/**/
|
||||
public interface UnitsOfForce: UnitsOfMeasurement
|
||||
/**
|
||||
* 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 interface UnitsOfTorque: UnitsOfMeasurement
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
public data object NewtonsMeters: UnitsOfTorque
|
||||
/**
|
||||
* Interface for units of mass.
|
||||
*/
|
||||
public interface UnitsOfMass : UnitsOfMeasurement
|
||||
|
||||
/**/
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
public interface UnitsOfMass: UnitsOfMeasurement
|
||||
/**
|
||||
* Interface for units of moment of inertia.
|
||||
*/
|
||||
public interface UnitsOfMomentOfInertia : UnitsOfMeasurement
|
||||
|
||||
public data object Kilograms : UnitsOfMass
|
||||
|
||||
/**/
|
||||
|
||||
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
|
||||
|
||||
// Установить позицию мотора
|
||||
suspend fun setPosition(value: Int) {
|
||||
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(
|
||||
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")
|
||||
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
|
||||
|
||||
// Установить давление
|
||||
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 мм")
|
||||
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
|
||||
|
||||
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