WIP: Простая конфигурация устройств через спецификации #12
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
package space.kscience.controls.constructor.dsl.core
|
||||
|
||||
/**
|
||||
* Класс для представления аннотации.
|
||||
*/
|
||||
public data class Annotation(
|
||||
val name: String,
|
||||
val properties: Map<String, Any>
|
||||
)
|
@ -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>
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package space.kscience.controls.constructor.dsl.core.equations
|
||||
|
||||
/**
|
||||
* Абстрактный базовый класс для уравнений.
|
||||
*/
|
||||
public sealed class EquationBase
|
@ -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()
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
@ -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.")
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
/**
|
||||
* Перечисление для бинарных операций над массивами.
|
||||
*/
|
||||
public enum class ArrayBinaryOperator {
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
MULTIPLY,
|
||||
DIVIDE
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
public sealed class ArrayExpression : Expression()
|
@ -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.")
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package space.kscience.controls.constructor.dsl.core.expressions
|
||||
|
||||
/**
|
||||
* Перечисление операторов между массивом и скаляром.
|
||||
*/
|
||||
public enum class ArrayScalarOperator {
|
||||
MULTIPLY,
|
||||
DIVIDE
|
||||
}
|
@ -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.")
|
||||
}
|
||||
}
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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.")
|
||||
}
|
||||
}
|
@ -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.")
|
||||
}
|
||||
}
|
@ -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.")
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>>
|
||||
)
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
)
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
/**
|
||||
* Перечисление каузальности переменной.
|
||||
*/
|
||||
public enum class Causality {
|
||||
INPUT,
|
||||
OUTPUT,
|
||||
INTERNAL
|
||||
}
|
@ -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)
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package space.kscience.controls.constructor.dsl.core.variables
|
||||
|
||||
/**
|
||||
* Перечисление типов изменчивости переменной.
|
||||
*/
|
||||
public enum class Variability {
|
||||
CONSTANT,
|
||||
PARAMETER,
|
||||
DISCRETE,
|
||||
CONTINUOUS
|
||||
}
|
@ -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() {}
|
||||
}
|
@ -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"))
|
||||
}
|
||||
}
|
@ -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"))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user
Use libs.versions for version storage or use
spclibs
reference