Add simple expressions and equations

This commit is contained in:
Maxim 2024-10-10 19:44:29 +03:00
parent 89d78c43bb
commit 3f09d20465
55 changed files with 2392 additions and 0 deletions

View File

@ -16,10 +16,13 @@ kscience{
useSerialization()
commonMain {
api(projects.controlsCore)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
implementation("org.jetbrains.kotlinx:multik-core:0.2.3")
}
commonTest{
implementation(spclibs.logback.classic)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
}
}

View File

@ -0,0 +1,9 @@
package space.kscience.controls.constructor.dsl.core
/**
* Класс для представления аннотации.
*/
public data class Annotation(
val name: String,
val properties: Map<String, Any>
)

View File

@ -0,0 +1,196 @@
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>
)

View File

@ -0,0 +1,19 @@
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)
}
}

View File

@ -0,0 +1,37 @@
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())
}
}

View File

@ -0,0 +1,80 @@
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)
}
}
}

View File

@ -0,0 +1,81 @@
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
}
}

View File

@ -0,0 +1,38 @@
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
}
}

View File

@ -0,0 +1,22 @@
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

View File

@ -0,0 +1,163 @@
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
}
}

View File

@ -0,0 +1,21 @@
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()
}
}

View File

@ -0,0 +1,12 @@
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()

View File

@ -0,0 +1,24 @@
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
}
}

View File

@ -0,0 +1,6 @@
package space.kscience.controls.constructor.dsl.core.equations
/**
* Абстрактный базовый класс для уравнений.
*/
public sealed class EquationBase

View File

@ -0,0 +1,11 @@
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()

View File

@ -0,0 +1,18 @@
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)
}
}

View File

@ -0,0 +1,30 @@
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)
}
}
}

View File

@ -0,0 +1,12 @@
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)
}

View File

@ -0,0 +1,18 @@
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)
}
}

View File

@ -0,0 +1,91 @@
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())
}

View File

@ -0,0 +1,37 @@
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.")
}
}

View File

@ -0,0 +1,11 @@
package space.kscience.controls.constructor.dsl.core.expressions
/**
* Перечисление для бинарных операций над массивами.
*/
public enum class ArrayBinaryOperator {
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE
}

View File

@ -0,0 +1,3 @@
package space.kscience.controls.constructor.dsl.core.expressions
public sealed class ArrayExpression : Expression()

View File

@ -0,0 +1,34 @@
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.")
}
}

View File

@ -0,0 +1,9 @@
package space.kscience.controls.constructor.dsl.core.expressions
/**
* Перечисление операторов между массивом и скаляром.
*/
public enum class ArrayScalarOperator {
MULTIPLY,
DIVIDE
}

View File

@ -0,0 +1,42 @@
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.")
}
}

View File

@ -0,0 +1,135 @@
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.")
}
}
}

View File

@ -0,0 +1,20 @@
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)
}
}

View File

@ -0,0 +1,30 @@
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)
}
}

View File

@ -0,0 +1,34 @@
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.")
}
}

View File

@ -0,0 +1,91 @@
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.")
}
}

View File

@ -0,0 +1,31 @@
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.")
}
}

View File

@ -0,0 +1,32 @@
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)
}
}

View File

@ -0,0 +1,14 @@
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
)

View File

@ -0,0 +1,50 @@
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)
}
}

View File

@ -0,0 +1,37 @@
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>>
)

View File

@ -0,0 +1,93 @@
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
}
}

View File

@ -0,0 +1,78 @@
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
}
}

View File

@ -0,0 +1,51 @@
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
}
}

View File

@ -0,0 +1,89 @@
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)
}

View File

@ -0,0 +1,13 @@
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
)

View File

@ -0,0 +1,28 @@
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)
}
}

View File

@ -0,0 +1,10 @@
package space.kscience.controls.constructor.dsl.core.variables
/**
* Перечисление каузальности переменной.
*/
public enum class Causality {
INPUT,
OUTPUT,
INTERNAL
}

View File

@ -0,0 +1,12 @@
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)

View File

@ -0,0 +1,22 @@
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.")
}
}
}

View File

@ -0,0 +1,13 @@
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)

View File

@ -0,0 +1,25 @@
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.")
}
}
}

View File

@ -0,0 +1,11 @@
package space.kscience.controls.constructor.dsl.core.variables
/**
* Перечисление типов изменчивости переменной.
*/
public enum class Variability {
CONSTANT,
PARAMETER,
DISCRETE,
CONTINUOUS
}

View File

@ -0,0 +1,28 @@
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() {}
}

View File

@ -0,0 +1,89 @@
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"))
}
}

View File

@ -0,0 +1,73 @@
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"))
}
}

View File

@ -0,0 +1,101 @@
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)
}
}

View File

@ -0,0 +1,29 @@
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)
}
}

View File

@ -0,0 +1,85 @@
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)
}
}

View File

@ -0,0 +1,41 @@
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)
}
}