From 9ee506b1d2996b3d46ea569e03d3a1862e4c1dff Mon Sep 17 00:00:00 2001 From: Iaroslav Postovalov Date: Thu, 25 Mar 2021 23:57:47 +0700 Subject: [PATCH] Some experiments with MST rendering --- README.md | 4 +- .../space/kscience/kmath/ast/astRendering.kt | 21 ++ kmath-ast/README.md | 42 ++- kmath-ast/docs/README-TEMPLATE.md | 36 ++ .../ast/rendering/LatexSyntaxRenderer.kt | 128 +++++++ .../ast/rendering/MathMLSyntaxRenderer.kt | 133 +++++++ .../kmath/ast/rendering/MathRenderer.kt | 102 ++++++ .../kmath/ast/rendering/MathSyntax.kt | 326 ++++++++++++++++++ .../kmath/ast/rendering/SyntaxRenderer.kt | 25 ++ .../kscience/kmath/ast/rendering/features.kt | 312 +++++++++++++++++ .../kscience/kmath/ast/rendering/stages.kt | 197 +++++++++++ .../space/kscience/kmath/estree/estree.kt | 4 +- .../kotlin/space/kscience/kmath/asm/asm.kt | 4 +- .../kotlin/space/kscience/kmath/ast/parser.kt | 3 +- .../kmath/ast/rendering/TestFeatures.kt | 90 +++++ .../kscience/kmath/ast/rendering/TestLatex.kt | 65 ++++ .../kmath/ast/rendering/TestMathML.kt | 84 +++++ .../kmath/ast/rendering/TestStages.kt | 28 ++ .../kscience/kmath/ast/rendering/TestUtils.kt | 41 +++ kmath-complex/README.md | 6 +- kmath-core/README.md | 6 +- .../space/kscience/kmath/operations/BigInt.kt | 3 +- kmath-ejml/README.md | 6 +- kmath-for-real/README.md | 6 +- kmath-functions/README.md | 6 +- kmath-nd4j/README.md | 6 +- 26 files changed, 1655 insertions(+), 29 deletions(-) create mode 100644 examples/src/main/kotlin/space/kscience/kmath/ast/astRendering.kt create mode 100644 kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt create mode 100644 kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt create mode 100644 kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt create mode 100644 kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt create mode 100644 kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt create mode 100644 kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt create mode 100644 kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt create mode 100644 kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt create mode 100644 kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestLatex.kt create mode 100644 kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestMathML.kt create mode 100644 kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt create mode 100644 kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt diff --git a/README.md b/README.md index 7080c757e..7b78d4531 100644 --- a/README.md +++ b/README.md @@ -259,8 +259,8 @@ repositories { } dependencies { - api("space.kscience:kmath-core:0.3.0-dev-3") - // api("kscience.kmath:kmath-core-jvm:0.3.0-dev-3") for jvm-specific version + api("space.kscience:kmath-core:0.3.0-dev-4") + // api("kscience.kmath:kmath-core-jvm:0.3.0-dev-4") for jvm-specific version } ``` diff --git a/examples/src/main/kotlin/space/kscience/kmath/ast/astRendering.kt b/examples/src/main/kotlin/space/kscience/kmath/ast/astRendering.kt new file mode 100644 index 000000000..a250ad800 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/ast/astRendering.kt @@ -0,0 +1,21 @@ +package space.kscience.kmath.ast + +import space.kscience.kmath.ast.rendering.FeaturedMathRendererWithPostProcess +import space.kscience.kmath.ast.rendering.LatexSyntaxRenderer +import space.kscience.kmath.ast.rendering.MathMLSyntaxRenderer +import space.kscience.kmath.ast.rendering.renderWithStringBuilder + +public fun main() { + val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(-12)".parseMath() + val syntax = FeaturedMathRendererWithPostProcess.Default.render(mst) + println("MathSyntax:") + println(syntax) + println() + val latex = LatexSyntaxRenderer.renderWithStringBuilder(syntax) + println("LaTeX:") + println(latex) + println() + val mathML = MathMLSyntaxRenderer.renderWithStringBuilder(syntax) + println("MathML:") + println(mathML) +} diff --git a/kmath-ast/README.md b/kmath-ast/README.md index ff954b914..44faa5cd5 100644 --- a/kmath-ast/README.md +++ b/kmath-ast/README.md @@ -12,7 +12,7 @@ Abstract syntax tree expression representation and related optimizations. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-ast:0.3.0-dev-3`. +The Maven coordinates of this project are `space.kscience:kmath-ast:0.3.0-dev-4`. **Gradle:** ```gradle @@ -23,7 +23,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-ast:0.3.0-dev-3' + implementation 'space.kscience:kmath-ast:0.3.0-dev-4' } ``` **Gradle Kotlin DSL:** @@ -35,7 +35,7 @@ repositories { } dependencies { - implementation("space.kscience:kmath-ast:0.3.0-dev-3") + implementation("space.kscience:kmath-ast:0.3.0-dev-4") } ``` @@ -111,3 +111,39 @@ var executable = function (constants, arguments) { #### Known issues - This feature uses `eval` which can be unavailable in several environments. + +## Rendering expressions + +kmath-ast also includes an extensible engine to display expressions in LaTeX or MathML syntax. + +Example usage: + +```kotlin +import space.kscience.kmath.ast.* +import space.kscience.kmath.ast.rendering.* + +public fun main() { + val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(-12)".parseMath() + val syntax = FeaturedMathRendererWithPostProcess.Default.render(mst) + val latex = LatexSyntaxRenderer.renderWithStringBuilder(syntax) + println("LaTeX:") + println(latex) + println() + val mathML = MathMLSyntaxRenderer.renderWithStringBuilder(syntax) + println("MathML:") + println(mathML) +} +``` + +Result LaTeX: + +![](http://chart.googleapis.com/chart?cht=tx&chl=e%5E%7B%5Csqrt%7Bx%7D%7D-%5Cfrac%7B%5Cfrac%7B%5Coperatorname%7Bsin%7D%5E%7B-1%7D%5C,%5Cleft(2%5C,x%5Cright)%7D%7B2%5Ctimes10%5E%7B10%7D%2Bx%5E%7B3%7D%7D%7D%7B-12%7D) + +Result MathML (embedding MathML is not allowed by GitHub Markdown): + +```html +ex-sin-12x2×1010+x3-12 +``` + +It is also possible to create custom algorithms of render, and even add support of other markup languages +(see API reference). diff --git a/kmath-ast/docs/README-TEMPLATE.md b/kmath-ast/docs/README-TEMPLATE.md index db071adb4..9ed44d584 100644 --- a/kmath-ast/docs/README-TEMPLATE.md +++ b/kmath-ast/docs/README-TEMPLATE.md @@ -78,3 +78,39 @@ var executable = function (constants, arguments) { #### Known issues - This feature uses `eval` which can be unavailable in several environments. + +## Rendering expressions + +kmath-ast also includes an extensible engine to display expressions in LaTeX or MathML syntax. + +Example usage: + +```kotlin +import space.kscience.kmath.ast.* +import space.kscience.kmath.ast.rendering.* + +public fun main() { + val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(-12)".parseMath() + val syntax = FeaturedMathRendererWithPostProcess.Default.render(mst) + val latex = LatexSyntaxRenderer.renderWithStringBuilder(syntax) + println("LaTeX:") + println(latex) + println() + val mathML = MathMLSyntaxRenderer.renderWithStringBuilder(syntax) + println("MathML:") + println(mathML) +} +``` + +Result LaTeX: + +![](http://chart.googleapis.com/chart?cht=tx&chl=e%5E%7B%5Csqrt%7Bx%7D%7D-%5Cfrac%7B%5Cfrac%7B%5Coperatorname%7Bsin%7D%5E%7B-1%7D%5C,%5Cleft(2%5C,x%5Cright)%7D%7B2%5Ctimes10%5E%7B10%7D%2Bx%5E%7B3%7D%7D%7D%7B-12%7D) + +Result MathML (embedding MathML is not allowed by GitHub Markdown): + +```html +ex-sin-12x2×1010+x3-12 +``` + +It is also possible to create custom algorithms of render, and even add support of other markup languages +(see API reference). diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt new file mode 100644 index 000000000..914da6d9f --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt @@ -0,0 +1,128 @@ +package space.kscience.kmath.ast.rendering + +/** + * [SyntaxRenderer] implementation for LaTeX. + * + * The generated string is a valid LaTeX fragment to be used in the Math Mode. + * + * Example usage: + * + * ``` + * \documentclass{article} + * \begin{document} + * \begin{equation} + * %code generated by the syntax renderer + * \end{equation} + * \end{document} + * ``` + * + * @author Iaroslav Postovalov + */ +public object LatexSyntaxRenderer : SyntaxRenderer { + public override fun render(node: MathSyntax, output: Appendable): Unit = output.run { + fun render(syntax: MathSyntax) = render(syntax, output) + + when (node) { + is NumberSyntax -> append(node.string) + is SymbolSyntax -> append(node.string) + + is OperatorNameSyntax -> { + append("\\operatorname{") + append(node.name) + append('}') + } + + is SpecialSymbolSyntax -> when (node.kind) { + SpecialSymbolSyntax.Kind.INFINITY -> append("\\infty") + } + + is OperandSyntax -> { + if (node.parentheses) append("\\left(") + render(node.operand) + if (node.parentheses) append("\\right)") + } + + is UnaryOperatorSyntax -> { + render(node.prefix) + append("\\,") + render(node.operand) + } + + is UnaryPlusSyntax -> { + append('+') + render(node.operand) + } + + is UnaryMinusSyntax -> { + append('-') + render(node.operand) + } + + is RadicalSyntax -> { + append("\\sqrt") + append('{') + render(node.operand) + append('}') + } + + is SuperscriptSyntax -> { + render(node.left) + append("^{") + render(node.right) + append('}') + } + + is SubscriptSyntax -> { + render(node.left) + append("_{") + render(node.right) + append('}') + } + + is BinaryOperatorSyntax -> { + render(node.prefix) + append("\\left(") + render(node.left) + append(',') + render(node.right) + append("\\right)") + } + + is BinaryPlusSyntax -> { + render(node.left) + append('+') + render(node.right) + } + + is BinaryMinusSyntax -> { + render(node.left) + append('-') + render(node.right) + } + + is FractionSyntax -> { + append("\\frac{") + render(node.left) + append("}{") + render(node.right) + append('}') + } + + is RadicalWithIndexSyntax -> { + append("\\sqrt") + append('[') + render(node.left) + append(']') + append('{') + render(node.right) + append('}') + } + + is MultiplicationSyntax -> { + render(node.left) + append(if (node.times) "\\times" else "\\,") + render(node.right) + } + } + } +} diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt new file mode 100644 index 000000000..6f194be86 --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt @@ -0,0 +1,133 @@ +package space.kscience.kmath.ast.rendering + +/** + * [SyntaxRenderer] implementation for MathML. + * + * The generated XML string is a valid MathML instance. + * + * @author Iaroslav Postovalov + */ +public object MathMLSyntaxRenderer : SyntaxRenderer { + public override fun render(node: MathSyntax, output: Appendable) { + output.append("") + render0(node, output) + output.append("") + } + + private fun render0(node: MathSyntax, output: Appendable): Unit = output.run { + fun tag(tagName: String, vararg attr: Pair, block: () -> Unit = {}) { + append('<') + append(tagName) + + if (attr.isNotEmpty()) { + append(' ') + var count = 0 + + for ((name, value) in attr) { + if (++count > 1) append(' ') + append(name) + append("=\"") + append(value) + append('"') + } + } + + append('>') + block() + append("') + } + + fun render(syntax: MathSyntax) = render0(syntax, output) + + when (node) { + is NumberSyntax -> tag("mn") { append(node.string) } + is SymbolSyntax -> tag("mi") { append(node.string) } + is OperatorNameSyntax -> tag("mo") { append(node.name) } + + is SpecialSymbolSyntax -> when (node.kind) { + SpecialSymbolSyntax.Kind.INFINITY -> tag("mo") { append("∞") } + } + + is OperandSyntax -> if (node.parentheses) { + tag("mfenced", "open" to "(", "close" to ")", "separators" to "") { + render(node.operand) + } + } else { + render(node.operand) + } + + is UnaryOperatorSyntax -> { + render(node.prefix) + tag("mspace", "width" to "0.167em") + render(node.operand) + } + + is UnaryPlusSyntax -> { + tag("mo") { append('+') } + render(node.operand) + } + + is UnaryMinusSyntax -> { + tag("mo") { append("-") } + render(node.operand) + } + + is RadicalSyntax -> tag("msqrt") { render(node.operand) } + + is SuperscriptSyntax -> tag("msup") { + tag("mrow") { render(node.left) } + tag("mrow") { render(node.right) } + } + + is SubscriptSyntax -> tag("msub") { + tag("mrow") { render(node.left) } + tag("mrow") { render(node.right) } + } + + is BinaryOperatorSyntax -> { + render(node.prefix) + + tag("mfenced", "open" to "(", "close" to ")", "separators" to "") { + render(node.left) + tag("mo") { append(',') } + render(node.right) + } + } + + is BinaryPlusSyntax -> { + render(node.left) + tag("mo") { append('+') } + render(node.right) + } + + is BinaryMinusSyntax -> { + render(node.left) + tag("mo") { append('-') } + render(node.right) + } + + is FractionSyntax -> tag("mfrac") { + tag("mrow") { + render(node.left) + } + + tag("mrow") { + render(node.right) + } + } + + is RadicalWithIndexSyntax -> tag("mroot") { + tag("mrow") { render(node.right) } + tag("mrow") { render(node.left) } + } + + is MultiplicationSyntax -> { + render(node.left) + if (node.times) tag("mo") { append("×") } else tag("mspace", "width" to "0.167em") + render(node.right) + } + } + } +} diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt new file mode 100644 index 000000000..afdf12b04 --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt @@ -0,0 +1,102 @@ +package space.kscience.kmath.ast.rendering + +import space.kscience.kmath.ast.MST + +/** + * Renders [MST] to [MathSyntax]. + * + * @author Iaroslav Postovalov + */ +public fun interface MathRenderer { + /** + * Renders [MST] to [MathSyntax]. + */ + public fun render(mst: MST): MathSyntax +} + +/** + * Implements [MST] render process with sequence of features. + * + * @property features The applied features. + * @author Iaroslav Postovalov + */ +public open class FeaturedMathRenderer(public val features: List) : MathRenderer { + public override fun render(mst: MST): MathSyntax { + for (feature in features) feature.render(this, mst)?.let { return it } + throw UnsupportedOperationException("Renderer $this has no appropriate feature to render node $mst.") + } + + /** + * Logical unit of [MST] rendering. + */ + public fun interface RenderFeature { + /** + * Renders [MST] to [MathSyntax] in the context of owning renderer. + */ + public fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? + } +} + +/** + * Extends [FeaturedMathRenderer] by adding post-processing stages. + * + * @property stages The applied stages. + * @author Iaroslav Postovalov + */ +public open class FeaturedMathRendererWithPostProcess( + features: List, + public val stages: List, +) : FeaturedMathRenderer(features) { + public override fun render(mst: MST): MathSyntax { + val res = super.render(mst) + for (stage in stages) stage.perform(res) + return res + } + + /** + * Logical unit of [MathSyntax] post-processing. + */ + public fun interface PostProcessStage { + /** + * Performs the specified action over [MathSyntax]. + */ + public fun perform(node: MathSyntax) + } + + public companion object { + /** + * The default setup of [FeaturedMathRendererWithPostProcess]. + */ + public val Default: FeaturedMathRendererWithPostProcess = FeaturedMathRendererWithPostProcess( + listOf( + // Printing known operations + BinaryPlus.Default, + BinaryMinus.Default, + UnaryPlus.Default, + UnaryMinus.Default, + Multiplication.Default, + Fraction.Default, + Power.Default, + SquareRoot.Default, + Exponential.Default, + InverseTrigonometricOperations.Default, + + // Fallback option for unknown operations - printing them as operator + BinaryOperator.Default, + UnaryOperator.Default, + + // Pretty printing for numerics + PrettyPrintFloats.Default, + PrettyPrintIntegers.Default, + + // Printing terminal nodes as string + PrintNumeric, + PrintSymbolic, + ), + listOf( + SimplifyParentheses.Default, + BetterMultiplication, + ) + ) + } +} diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt new file mode 100644 index 000000000..4c85adcfc --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt @@ -0,0 +1,326 @@ +package space.kscience.kmath.ast.rendering + +/** + * Mathematical typography syntax node. + * + * @author Iaroslav Postovalov + */ +public sealed class MathSyntax { + /** + * The parent node of this syntax node. + */ + public var parent: MathSyntax? = null +} + +/** + * Terminal node, which should not have any children nodes. + * + * @author Iaroslav Postovalov + */ +public sealed class TerminalSyntax : MathSyntax() + +/** + * Node containing a certain operation. + * + * @author Iaroslav Postovalov + */ +public sealed class OperationSyntax : MathSyntax() { + /** + * The operation token. + */ + public abstract val operation: String +} + +/** + * Unary node, which has only one child. + * + * @author Iaroslav Postovalov + */ +public sealed class UnarySyntax : OperationSyntax() { + /** + * The operand of this node. + */ + public abstract val operand: MathSyntax +} + +/** + * Binary node, which has only two children. + * + * @author Iaroslav Postovalov + */ +public sealed class BinarySyntax : OperationSyntax() { + /** + * The left-hand side operand. + */ + public abstract val left: MathSyntax + + /** + * The right-hand side operand. + */ + public abstract val right: MathSyntax +} + +/** + * Represents a number. + * + * @property string The digits of number. + * @author Iaroslav Postovalov + */ +public data class NumberSyntax(public var string: String) : TerminalSyntax() + +/** + * Represents a symbol. + * + * @property string The symbol. + * @author Iaroslav Postovalov + */ +public data class SymbolSyntax(public var string: String) : TerminalSyntax() + +/** + * Represents special typing for operator name. + * + * @property name The operator name. + * @see BinaryOperatorSyntax + * @see UnaryOperatorSyntax + * @author Iaroslav Postovalov + */ +public data class OperatorNameSyntax(public var name: String) : TerminalSyntax() + +/** + * Represents a usage of special symbols. + * + * @property kind The kind of symbol. + * @author Iaroslav Postovalov + */ +public data class SpecialSymbolSyntax(public var kind: Kind) : TerminalSyntax() { + /** + * The kind of symbol. + */ + public enum class Kind { + /** + * The infinity (∞) symbol. + */ + INFINITY, + } +} + +/** + * Represents operand of a certain operator wrapped with parentheses or not. + * + * @property operand The operand. + * @property parentheses Whether the operand should be wrapped with parentheses. + * @author Iaroslav Postovalov + */ +public data class OperandSyntax( + public val operand: MathSyntax, + public var parentheses: Boolean, +) : MathSyntax() { + init { + operand.parent = this + } +} + +/** + * Represents unary, prefix operator syntax (like f x). + * + * @property prefix The prefix. + * @author Iaroslav Postovalov + */ +public data class UnaryOperatorSyntax( + public override val operation: String, + public var prefix: MathSyntax, + public override val operand: OperandSyntax, +) : UnarySyntax() { + init { + operand.parent = this + } +} + +/** + * Represents prefix, unary plus operator. + * + * @author Iaroslav Postovalov + */ +public data class UnaryPlusSyntax( + public override val operation: String, + public override val operand: OperandSyntax, +) : UnarySyntax() { + init { + operand.parent = this + } +} + +/** + * Represents prefix, unary minus operator. + * + * @author Iaroslav Postovalov + */ +public data class UnaryMinusSyntax( + public override val operation: String, + public override val operand: OperandSyntax, +) : UnarySyntax() { + init { + operand.parent = this + } +} + +/** + * Represents radical with a node inside it. + * + * @property operand The radicand. + * @author Iaroslav Postovalov + */ +public data class RadicalSyntax( + public override val operation: String, + public override val operand: MathSyntax, +) : UnarySyntax() { + init { + operand.parent = this + } +} + +/** + * Represents a syntax node with superscript (usually, for exponentiation). + * + * @property left The node. + * @property right The superscript. + * @author Iaroslav Postovalov + */ +public data class SuperscriptSyntax( + public override val operation: String, + public override val left: MathSyntax, + public override val right: MathSyntax, +) : BinarySyntax() { + init { + left.parent = this + right.parent = this + } +} + +/** + * Represents a syntax node with subscript. + * + * @property left The node. + * @property right The subscript. + * @author Iaroslav Postovalov + */ +public data class SubscriptSyntax( + public override val operation: String, + public override val left: MathSyntax, + public override val right: MathSyntax, +) : BinarySyntax() { + init { + left.parent = this + right.parent = this + } +} + +/** + * Represents binary, prefix operator syntax (like f(a, b)). + * + * @property prefix The prefix. + * @author Iaroslav Postovalov + */ +public data class BinaryOperatorSyntax( + public override val operation: String, + public var prefix: MathSyntax, + public override val left: MathSyntax, + public override val right: MathSyntax, +) : BinarySyntax() { + init { + left.parent = this + right.parent = this + } +} + +/** + * Represents binary, infix addition. + * + * @param left The augend. + * @param right The addend. + * @author Iaroslav Postovalov + */ +public data class BinaryPlusSyntax( + public override val operation: String, + public override val left: OperandSyntax, + public override val right: OperandSyntax, +) : BinarySyntax() { + init { + left.parent = this + right.parent = this + } +} + +/** + * Represents binary, infix subtraction. + * + * @param left The minuend. + * @param right The subtrahend. + * @author Iaroslav Postovalov + */ +public data class BinaryMinusSyntax( + public override val operation: String, + public override val left: OperandSyntax, + public override val right: OperandSyntax, +) : BinarySyntax() { + init { + left.parent = this + right.parent = this + } +} + +/** + * Represents fraction with numerator and denominator. + * + * @property left The numerator. + * @property right The denominator. + * @author Iaroslav Postovalov + */ +public data class FractionSyntax( + public override val operation: String, + public override val left: MathSyntax, + public override val right: MathSyntax, +) : BinarySyntax() { + init { + left.parent = this + right.parent = this + } +} + +/** + * Represents radical syntax with index. + * + * @property left The index. + * @property right The radicand. + * @author Iaroslav Postovalov + */ +public data class RadicalWithIndexSyntax( + public override val operation: String, + public override val left: MathSyntax, + public override val right: MathSyntax, +) : BinarySyntax() { + init { + left.parent = this + right.parent = this + } +} + +/** + * Represents binary, infix multiplication in the form of coefficient (2 x) or with operator (x×2). + * + * @property left The multiplicand. + * @property right The multiplier. + * @property times whether the times (×) symbol should be used. + * @author Iaroslav Postovalov + */ +public data class MultiplicationSyntax( + public override val operation: String, + public override val left: OperandSyntax, + public override val right: OperandSyntax, + public var times: Boolean, +) : BinarySyntax() { + init { + left.parent = this + right.parent = this + } +} diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt new file mode 100644 index 000000000..fcc79f76b --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt @@ -0,0 +1,25 @@ +package space.kscience.kmath.ast.rendering + +/** + * Abstraction of writing [MathSyntax] as a string of an actual markup language. Typical implementation should + * involve traversal of MathSyntax with handling each its subtype. + * + * @author Iaroslav Postovalov + */ +public fun interface SyntaxRenderer { + /** + * Renders the [MathSyntax] to [output]. + */ + public fun render(node: MathSyntax, output: Appendable) +} + +/** + * Calls [SyntaxRenderer.render] with given [node] and a new [StringBuilder] instance, and returns its content. + * + * @author Iaroslav Postovalov + */ +public fun SyntaxRenderer.renderWithStringBuilder(node: MathSyntax): String { + val sb = StringBuilder() + render(node, sb) + return sb.toString() +} diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt new file mode 100644 index 000000000..6e66d3ca3 --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt @@ -0,0 +1,312 @@ +package space.kscience.kmath.ast.rendering + +import space.kscience.kmath.ast.MST +import space.kscience.kmath.ast.rendering.FeaturedMathRenderer.RenderFeature +import space.kscience.kmath.operations.* +import kotlin.reflect.KClass + +/** + * Prints any [MST.Symbolic] as a [SymbolSyntax] containing the [MST.Symbolic.value] of it. + * + * @author Iaroslav Postovalov + */ +public object PrintSymbolic : RenderFeature { + public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { + if (node !is MST.Symbolic) return null + return SymbolSyntax(string = node.value) + } +} + +/** + * Prints any [MST.Numeric] as a [NumberSyntax] containing the [Any.toString] result of it. + * + * @author Iaroslav Postovalov + */ +public object PrintNumeric : RenderFeature { + public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { + if (node !is MST.Numeric) return null + return NumberSyntax(string = node.value.toString()) + } +} + +private fun printSignedNumberString(s: String): MathSyntax { + if (s.startsWith('-')) + return UnaryMinusSyntax( + operation = GroupOperations.MINUS_OPERATION, + operand = OperandSyntax( + operand = NumberSyntax(string = s.removePrefix("-")), + parentheses = true, + ), + ) + + return NumberSyntax(string = s) +} + +/** + * Special printing for numeric types which are printed in form of + * *('-'? (DIGIT+ ('.' DIGIT+)? ('E' '-'? DIGIT+)? | 'Infinity')) | 'NaN'*. + * + * @property types The suitable types. + */ +public class PrettyPrintFloats(public val types: Set>) : RenderFeature { + public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { + if (node !is MST.Numeric || node.value::class !in types) return null + val toString = node.value.toString().removeSuffix(".0") + + if ('E' in toString) { + val (beforeE, afterE) = toString.split('E') + val significand = beforeE.toDouble().toString().removeSuffix(".0") + val exponent = afterE.toDouble().toString().removeSuffix(".0") + + return MultiplicationSyntax( + operation = RingOperations.TIMES_OPERATION, + left = OperandSyntax(operand = NumberSyntax(significand), parentheses = true), + right = OperandSyntax( + operand = SuperscriptSyntax( + operation = PowerOperations.POW_OPERATION, + left = NumberSyntax(string = "10"), + right = printSignedNumberString(exponent), + ), + parentheses = true, + ), + times = true, + ) + } + + if (toString.endsWith("Infinity")) { + val infty = SpecialSymbolSyntax(SpecialSymbolSyntax.Kind.INFINITY) + + if (toString.startsWith('-')) + return UnaryMinusSyntax( + operation = GroupOperations.MINUS_OPERATION, + operand = OperandSyntax(operand = infty, parentheses = true), + ) + + return infty + } + + return printSignedNumberString(toString) + } + + public companion object { + /** + * The default instance containing [Float], and [Double]. + */ + public val Default: PrettyPrintFloats = PrettyPrintFloats(setOf(Float::class, Double::class)) + } +} + +/** + * Special printing for numeric types which are printed in form of *'-'? DIGIT+*. + * + * @property types The suitable types. + */ +public class PrettyPrintIntegers(public val types: Set>) : RenderFeature { + public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { + if (node !is MST.Numeric || node.value::class !in types) + return null + + return printSignedNumberString(node.value.toString()) + } + + public companion object { + /** + * The default instance containing [Byte], [Short], [Int], and [Long]. + */ + public val Default: PrettyPrintIntegers = + PrettyPrintIntegers(setOf(Byte::class, Short::class, Int::class, Long::class)) + } +} + +/** + * Abstract printing of unary operations which discards [MST] if their operation is not in [operations] or its type is + * not [MST.Unary]. + * + * @param operations the allowed operations. If `null`, any operation is accepted. + */ +public abstract class Unary(public val operations: Collection?) : RenderFeature { + /** + * The actual render function. + */ + protected abstract fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax? + + public final override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { + if (node !is MST.Unary || operations != null && node.operation !in operations) return null + return render0(renderer, node) + } +} + +/** + * Abstract printing of unary operations which discards [MST] if their operation is not in [operations] or its type is + * not [MST.Binary]. + * + * @property operations the allowed operations. If `null`, any operation is accepted. + */ +public abstract class Binary(public val operations: Collection?) : RenderFeature { + /** + * The actual render function. + */ + protected abstract fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax? + + public final override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { + if (node !is MST.Binary || operations != null && node.operation !in operations) return null + return render0(renderer, node) + } +} + +public class BinaryPlus(operations: Collection?) : Binary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = BinaryPlusSyntax( + operation = node.operation, + left = OperandSyntax(parent.render(node.left), true), + right = OperandSyntax(parent.render(node.right), true), + ) + + public companion object { + public val Default: BinaryPlus = BinaryPlus(setOf(GroupOperations.PLUS_OPERATION)) + } +} + +public class BinaryMinus(operations: Collection?) : Binary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = BinaryMinusSyntax( + operation = node.operation, + left = OperandSyntax(operand = parent.render(node.left), parentheses = true), + right = OperandSyntax(operand = parent.render(node.right), parentheses = true), + ) + + public companion object { + public val Default: BinaryMinus = BinaryMinus(setOf(GroupOperations.MINUS_OPERATION)) + } +} + +public class UnaryPlus(operations: Collection?) : Unary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryPlusSyntax( + operation = node.operation, + operand = OperandSyntax(operand = parent.render(node.value), parentheses = true), + ) + + public companion object { + public val Default: UnaryPlus = UnaryPlus(setOf(GroupOperations.PLUS_OPERATION)) + } +} + +public class UnaryMinus(operations: Collection?) : Unary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryMinusSyntax( + operation = node.operation, + operand = OperandSyntax(operand = parent.render(node.value), parentheses = true), + ) + + public companion object { + public val Default: UnaryMinus = UnaryMinus(setOf(GroupOperations.MINUS_OPERATION)) + } +} + +public class Fraction(operations: Collection?) : Binary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = FractionSyntax( + operation = node.operation, + left = parent.render(node.left), + right = parent.render(node.right), + ) + + public companion object { + public val Default: Fraction = Fraction(setOf(FieldOperations.DIV_OPERATION)) + } +} + +public class BinaryOperator(operations: Collection?) : Binary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = BinaryOperatorSyntax( + operation = node.operation, + prefix = OperatorNameSyntax(name = node.operation), + left = parent.render(node.left), + right = parent.render(node.right), + ) + + public companion object { + public val Default: BinaryOperator = BinaryOperator(null) + } +} + +public class UnaryOperator(operations: Collection?) : Unary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryOperatorSyntax( + operation = node.operation, + prefix = OperatorNameSyntax(node.operation), + operand = OperandSyntax(parent.render(node.value), true), + ) + + public companion object { + public val Default: UnaryOperator = UnaryOperator(null) + } +} + +public class Power(operations: Collection?) : Binary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = SuperscriptSyntax( + operation = node.operation, + left = OperandSyntax(parent.render(node.left), true), + right = OperandSyntax(parent.render(node.right), true), + ) + + public companion object { + public val Default: Power = Power(setOf(PowerOperations.POW_OPERATION)) + } +} + +public class SquareRoot(operations: Collection?) : Unary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = + RadicalSyntax(operation = node.operation, operand = parent.render(node.value)) + + public companion object { + public val Default: SquareRoot = SquareRoot(setOf(PowerOperations.SQRT_OPERATION)) + } +} + +public class Exponential(operations: Collection?) : Unary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = SuperscriptSyntax( + operation = node.operation, + left = SymbolSyntax(string = "e"), + right = parent.render(node.value), + ) + + public companion object { + public val Default: Exponential = Exponential(setOf(ExponentialOperations.EXP_OPERATION)) + } +} + +public class Multiplication(operations: Collection?) : Binary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = MultiplicationSyntax( + operation = node.operation, + left = OperandSyntax(operand = parent.render(node.left), parentheses = true), + right = OperandSyntax(operand = parent.render(node.right), parentheses = true), + times = true, + ) + + public companion object { + public val Default: Multiplication = Multiplication(setOf( + RingOperations.TIMES_OPERATION, + )) + } +} + +public class InverseTrigonometricOperations(operations: Collection?) : Unary(operations) { + public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryOperatorSyntax( + operation = node.operation, + prefix = SuperscriptSyntax( + operation = PowerOperations.POW_OPERATION, + left = OperatorNameSyntax(name = node.operation.removePrefix("a")), + right = UnaryMinusSyntax( + operation = GroupOperations.MINUS_OPERATION, + operand = OperandSyntax(operand = NumberSyntax(string = "1"), parentheses = true), + ), + ), + operand = OperandSyntax(operand = parent.render(node.value), parentheses = true), + ) + + public companion object { + public val Default: InverseTrigonometricOperations = InverseTrigonometricOperations(setOf( + TrigonometricOperations.ACOS_OPERATION, + TrigonometricOperations.ASIN_OPERATION, + TrigonometricOperations.ATAN_OPERATION, + ExponentialOperations.ACOSH_OPERATION, + ExponentialOperations.ASINH_OPERATION, + ExponentialOperations.ATANH_OPERATION, + )) + } +} diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt new file mode 100644 index 000000000..c183f6ace --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt @@ -0,0 +1,197 @@ +package space.kscience.kmath.ast.rendering + +import space.kscience.kmath.operations.FieldOperations +import space.kscience.kmath.operations.GroupOperations +import space.kscience.kmath.operations.PowerOperations +import space.kscience.kmath.operations.RingOperations + +/** + * Removes unnecessary times (×) symbols from [MultiplicationSyntax]. + * + * @author Iaroslav Postovalov + */ +public object BetterMultiplication : FeaturedMathRendererWithPostProcess.PostProcessStage { + public override fun perform(node: MathSyntax) { + when (node) { + is NumberSyntax -> Unit + is SymbolSyntax -> Unit + is OperatorNameSyntax -> Unit + is SpecialSymbolSyntax -> Unit + is OperandSyntax -> perform(node.operand) + + is UnaryOperatorSyntax -> { + perform(node.prefix) + perform(node.operand) + } + + is UnaryPlusSyntax -> perform(node.operand) + is UnaryMinusSyntax -> perform(node.operand) + is RadicalSyntax -> perform(node.operand) + + is SuperscriptSyntax -> { + perform(node.left) + perform(node.right) + } + + is SubscriptSyntax -> { + perform(node.left) + perform(node.right) + } + + is BinaryOperatorSyntax -> { + perform(node.prefix) + perform(node.left) + perform(node.right) + } + + is BinaryPlusSyntax -> { + perform(node.left) + perform(node.right) + } + + is BinaryMinusSyntax -> { + perform(node.left) + perform(node.right) + } + + is FractionSyntax -> { + perform(node.left) + perform(node.right) + } + + is RadicalWithIndexSyntax -> { + perform(node.left) + perform(node.right) + } + + is MultiplicationSyntax -> { + node.times = node.right.operand is NumberSyntax && !node.right.parentheses + || node.left.operand is NumberSyntax && node.right.operand is FractionSyntax + || node.left.operand is NumberSyntax && node.right.operand is NumberSyntax + || node.left.operand is NumberSyntax && node.right.operand is SuperscriptSyntax && node.right.operand.left is NumberSyntax + + perform(node.left) + perform(node.right) + } + } + } +} + +/** + * Removes unnecessary parentheses from [OperandSyntax]. + * + * @property precedenceFunction Returns the precedence number for syntax node. Higher number is lower priority. + * @author Iaroslav Postovalov + */ +public class SimplifyParentheses(public val precedenceFunction: (MathSyntax) -> Int) : + FeaturedMathRendererWithPostProcess.PostProcessStage { + public override fun perform(node: MathSyntax) { + when (node) { + is NumberSyntax -> Unit + is SymbolSyntax -> Unit + is OperatorNameSyntax -> Unit + is SpecialSymbolSyntax -> Unit + + is OperandSyntax -> { + val isRightOfSuperscript = + (node.parent is SuperscriptSyntax) && (node.parent as SuperscriptSyntax).right === node + + val precedence = precedenceFunction(node.operand) + + val needParenthesesByPrecedence = when (val parent = node.parent) { + null -> false + + is BinarySyntax -> { + val parentPrecedence = precedenceFunction(parent) + + parentPrecedence < precedence || + parentPrecedence == precedence && parentPrecedence != 0 && node === parent.right + } + + else -> precedence > precedenceFunction(parent) + } + + node.parentheses = !isRightOfSuperscript + && (needParenthesesByPrecedence || node.parent is UnaryOperatorSyntax) + + perform(node.operand) + } + + is UnaryOperatorSyntax -> { + perform(node.prefix) + perform(node.operand) + } + + is UnaryPlusSyntax -> perform(node.operand) + is UnaryMinusSyntax -> { + perform(node.operand) + } + is RadicalSyntax -> perform(node.operand) + + is SuperscriptSyntax -> { + perform(node.left) + perform(node.right) + } + + is SubscriptSyntax -> { + perform(node.left) + perform(node.right) + } + + is BinaryOperatorSyntax -> { + perform(node.prefix) + perform(node.left) + perform(node.right) + } + + is BinaryPlusSyntax -> { + perform(node.left) + perform(node.right) + } + + is BinaryMinusSyntax -> { + perform(node.left) + perform(node.right) + } + + is FractionSyntax -> { + perform(node.left) + perform(node.right) + } + + is MultiplicationSyntax -> { + perform(node.left) + perform(node.right) + } + + is RadicalWithIndexSyntax -> { + perform(node.left) + perform(node.right) + } + } + } + + public companion object { + /** + * The default configuration of [SimplifyParentheses] where power is 1, multiplicative operations are 2, + * additive operations are 3. + */ + public val Default: SimplifyParentheses = SimplifyParentheses { + when (it) { + is TerminalSyntax -> 0 + is UnarySyntax -> 2 + + is BinarySyntax -> when (it.operation) { + PowerOperations.POW_OPERATION -> 1 + RingOperations.TIMES_OPERATION -> 3 + FieldOperations.DIV_OPERATION -> 3 + GroupOperations.MINUS_OPERATION -> 4 + GroupOperations.PLUS_OPERATION -> 4 + else -> 0 + } + + else -> 0 + } + } + } +} diff --git a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt index 0bd9a386d..456a2ba07 100644 --- a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt +++ b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt @@ -68,7 +68,7 @@ internal fun MST.compileWith(algebra: Algebra): Expression { /** * Compiles an [MST] to ESTree generated expression using given algebra. * - * @author Alexander Nozik. + * @author Iaroslav Postovalov */ public fun Algebra.expression(mst: MST): Expression = mst.compileWith(this) @@ -76,7 +76,7 @@ public fun Algebra.expression(mst: MST): Expression = /** * Optimizes performance of an [MstExpression] by compiling it into ESTree generated expression. * - * @author Alexander Nozik. + * @author Iaroslav Postovalov */ public fun MstExpression>.compile(): Expression = mst.compileWith(algebra) diff --git a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt index 8875bd715..369fe136b 100644 --- a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt +++ b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt @@ -73,7 +73,7 @@ internal fun MST.compileWith(type: Class, algebra: Algebra): Exp /** * Compiles an [MST] to ASM using given algebra. * - * @author Alexander Nozik. + * @author Alexander Nozik */ public inline fun Algebra.expression(mst: MST): Expression = mst.compileWith(T::class.java, this) @@ -81,7 +81,7 @@ public inline fun Algebra.expression(mst: MST): Expression< /** * Optimizes performance of an [MstExpression] using ASM codegen. * - * @author Alexander Nozik. + * @author Alexander Nozik */ public inline fun MstExpression>.compile(): Expression = mst.compileWith(T::class.java, algebra) diff --git a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/ast/parser.kt b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/ast/parser.kt index 9a38ce81a..8ecb0adda 100644 --- a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/ast/parser.kt +++ b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/ast/parser.kt @@ -21,7 +21,8 @@ import space.kscience.kmath.operations.RingOperations /** * better-parse implementation of grammar defined in the ArithmeticsEvaluator.g4. * - * @author Alexander Nozik and Iaroslav Postovalov + * @author Alexander Nozik + * @author Iaroslav Postovalov */ public object ArithmeticsEvaluator : Grammar() { // TODO replace with "...".toRegex() when better-parse 0.4.1 is released diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt new file mode 100644 index 000000000..b10f7ed4e --- /dev/null +++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt @@ -0,0 +1,90 @@ +package space.kscience.kmath.ast.rendering + +import space.kscience.kmath.ast.MST.Numeric +import space.kscience.kmath.ast.rendering.TestUtils.testLatex +import kotlin.test.Test + +internal class TestFeatures { + @Test + fun printSymbolic() = testLatex("x", "x") + + @Test + fun printNumeric() { + val num = object : Number() { + override fun toByte(): Byte = throw UnsupportedOperationException() + override fun toChar(): Char = throw UnsupportedOperationException() + override fun toDouble(): Double = throw UnsupportedOperationException() + override fun toFloat(): Float = throw UnsupportedOperationException() + override fun toInt(): Int = throw UnsupportedOperationException() + override fun toLong(): Long = throw UnsupportedOperationException() + override fun toShort(): Short = throw UnsupportedOperationException() + override fun toString(): String = "foo" + } + + testLatex(Numeric(num), "foo") + } + + @Test + fun prettyPrintFloats() { + testLatex(Numeric(Double.NaN), "NaN") + testLatex(Numeric(Double.POSITIVE_INFINITY), "\\infty") + testLatex(Numeric(Double.NEGATIVE_INFINITY), "-\\infty") + testLatex(Numeric(1.0), "1") + testLatex(Numeric(-1.0), "-1") + testLatex(Numeric(1.42), "1.42") + testLatex(Numeric(-1.42), "-1.42") + testLatex(Numeric(1.1e10), "1.1\\times10^{10}") + testLatex(Numeric(1.1e-10), "1.1\\times10^{-10}") + testLatex(Numeric(-1.1e-10), "-1.1\\times10^{-10}") + testLatex(Numeric(-1.1e10), "-1.1\\times10^{10}") + } + + @Test + fun prettyPrintIntegers() { + testLatex(Numeric(42), "42") + testLatex(Numeric(-42), "-42") + } + + @Test + fun binaryPlus() = testLatex("2+2", "2+2") + + @Test + fun binaryMinus() = testLatex("2-2", "2-2") + + @Test + fun fraction() = testLatex("2/2", "\\frac{2}{2}") + + @Test + fun binaryOperator() = testLatex("f(x, y)", "\\operatorname{f}\\left(x,y\\right)") + + @Test + fun unaryOperator() = testLatex("f(x)", "\\operatorname{f}\\,\\left(x\\right)") + + @Test + fun power() = testLatex("x^y", "x^{y}") + + @Test + fun squareRoot() = testLatex("sqrt(x)", "\\sqrt{x}") + + @Test + fun exponential() = testLatex("exp(x)", "e^{x}") + + @Test + fun multiplication() = testLatex("x*1", "x\\times1") + + @Test + fun inverseTrigonometry() { + testLatex("asin(x)", "\\operatorname{sin}^{-1}\\,\\left(x\\right)") + testLatex("asinh(x)", "\\operatorname{sinh}^{-1}\\,\\left(x\\right)") + testLatex("acos(x)", "\\operatorname{cos}^{-1}\\,\\left(x\\right)") + testLatex("acosh(x)", "\\operatorname{cosh}^{-1}\\,\\left(x\\right)") + testLatex("atan(x)", "\\operatorname{tan}^{-1}\\,\\left(x\\right)") + testLatex("atanh(x)", "\\operatorname{tanh}^{-1}\\,\\left(x\\right)") + } + +// @Test +// fun unaryPlus() { +// testLatex("+1", "+1") +// testLatex("+1", "++1") +// } +} diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestLatex.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestLatex.kt new file mode 100644 index 000000000..9c1009042 --- /dev/null +++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestLatex.kt @@ -0,0 +1,65 @@ +package space.kscience.kmath.ast.rendering + +import space.kscience.kmath.ast.MST +import space.kscience.kmath.ast.rendering.TestUtils.testLatex +import space.kscience.kmath.operations.GroupOperations +import kotlin.test.Test + +internal class TestLatex { + @Test + fun number() = testLatex("42", "42") + + @Test + fun symbol() = testLatex("x", "x") + + @Test + fun operatorName() = testLatex("sin(1)", "\\operatorname{sin}\\,\\left(1\\right)") + + @Test + fun specialSymbol() = testLatex(MST.Numeric(Double.POSITIVE_INFINITY), "\\infty") + + @Test + fun operand() { + testLatex("sin(1)", "\\operatorname{sin}\\,\\left(1\\right)") + testLatex("1+1", "1+1") + } + + @Test + fun unaryOperator() = testLatex("sin(1)", "\\operatorname{sin}\\,\\left(1\\right)") + + @Test + fun unaryPlus() = testLatex(MST.Unary(GroupOperations.PLUS_OPERATION, MST.Numeric(1)), "+1") + + @Test + fun unaryMinus() = testLatex("-x", "-x") + + @Test + fun radical() = testLatex("sqrt(x)", "\\sqrt{x}") + + @Test + fun superscript() = testLatex("x^y", "x^{y}") + + @Test + fun subscript() = testLatex(SubscriptSyntax("", SymbolSyntax("x"), NumberSyntax("123")), "x_{123}") + + @Test + fun binaryOperator() = testLatex("f(x, y)", "\\operatorname{f}\\left(x,y\\right)") + + @Test + fun binaryPlus() = testLatex("x+x", "x+x") + + @Test + fun binaryMinus() = testLatex("x-x", "x-x") + + @Test + fun fraction() = testLatex("x/x", "\\frac{x}{x}") + + @Test + fun radicalWithIndex() = testLatex(RadicalWithIndexSyntax("", SymbolSyntax("x"), SymbolSyntax("y")), "\\sqrt[x]{y}") + + @Test + fun multiplication() { + testLatex("x*1", "x\\times1") + testLatex("1*x", "1\\,x") + } +} diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestMathML.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestMathML.kt new file mode 100644 index 000000000..c9a462840 --- /dev/null +++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestMathML.kt @@ -0,0 +1,84 @@ +package space.kscience.kmath.ast.rendering + +import space.kscience.kmath.ast.MST +import space.kscience.kmath.ast.rendering.TestUtils.testMathML +import space.kscience.kmath.operations.GroupOperations +import kotlin.test.Test + +internal class TestMathML { + @Test + fun number() = testMathML("42", "42") + + @Test + fun symbol() = testMathML("x", "x") + + @Test + fun operatorName() = testMathML( + "sin(1)", + "sin1", + ) + + @Test + fun specialSymbol() = testMathML(MST.Numeric(Double.POSITIVE_INFINITY), "") + + @Test + fun operand() { + testMathML( + "sin(1)", + "sin1", + ) + + testMathML("1+1", "1+1") + } + + @Test + fun unaryOperator() = testMathML( + "sin(1)", + "sin1", + ) + + @Test + fun unaryPlus() = + testMathML(MST.Unary(GroupOperations.PLUS_OPERATION, MST.Numeric(1)), "+1") + + @Test + fun unaryMinus() = testMathML("-x", "-x") + + @Test + fun radical() = testMathML("sqrt(x)", "x") + + @Test + fun superscript() = testMathML("x^y", "xy") + + @Test + fun subscript() = testMathML( + SubscriptSyntax("", SymbolSyntax("x"), NumberSyntax("123")), + "x123", + ) + + @Test + fun binaryOperator() = testMathML( + "f(x, y)", + "fx,y", + ) + + @Test + fun binaryPlus() = testMathML("x+x", "x+x") + + @Test + fun binaryMinus() = testMathML("x-x", "x-x") + + @Test + fun fraction() = testMathML("x/x", "xx") + + @Test + fun radicalWithIndex() = + testMathML(RadicalWithIndexSyntax("", SymbolSyntax("x"), SymbolSyntax("y")), + "yx") + + @Test + fun multiplication() { + testMathML("x*1", "x×1") + testMathML("1*x", "1x") + } +} diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt new file mode 100644 index 000000000..56a799c2c --- /dev/null +++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt @@ -0,0 +1,28 @@ +package space.kscience.kmath.ast.rendering + +import space.kscience.kmath.ast.rendering.TestUtils.testLatex +import kotlin.test.Test + +internal class TestStages { + @Test + fun betterMultiplication() { + testLatex("a*1", "a\\times1") + testLatex("1*(2/3)", "1\\times\\left(\\frac{2}{3}\\right)") + testLatex("1*1", "1\\times1") + testLatex("2e10", "2\\times10^{10}") + testLatex("2*x", "2\\,x") + testLatex("2*(x+1)", "2\\,\\left(x+1\\right)") + testLatex("x*y", "x\\,y") + } + + @Test + fun parentheses() { + testLatex("(x+1)", "x+1") + testLatex("x*x*x", "x\\,x\\,x") + testLatex("(x+x)*x", "\\left(x+x\\right)\\,x") + testLatex("x+x*x", "x+x\\,x") + testLatex("x+x^x*x+x", "x+x^{x}\\,x+x") + testLatex("(x+x)^x+x*x", "\\left(x+x\\right)^{x}+x\\,x") + testLatex("x^(x+x)", "x^{x+x}") + } +} diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt new file mode 100644 index 000000000..e6359bcc9 --- /dev/null +++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt @@ -0,0 +1,41 @@ +package space.kscience.kmath.ast.rendering + +import space.kscience.kmath.ast.MST +import space.kscience.kmath.ast.parseMath +import kotlin.test.assertEquals + +internal object TestUtils { + private fun mathSyntax(mst: MST) = FeaturedMathRendererWithPostProcess.Default.render(mst) + private fun latex(mst: MST) = LatexSyntaxRenderer.renderWithStringBuilder(mathSyntax(mst)) + private fun mathML(mst: MST) = MathMLSyntaxRenderer.renderWithStringBuilder(mathSyntax(mst)) + + internal fun testLatex(mst: MST, expectedLatex: String) = assertEquals( + expected = expectedLatex, + actual = latex(mst), + ) + + internal fun testLatex(expression: String, expectedLatex: String) = assertEquals( + expected = expectedLatex, + actual = latex(expression.parseMath()), + ) + + internal fun testLatex(expression: MathSyntax, expectedLatex: String) = assertEquals( + expected = expectedLatex, + actual = LatexSyntaxRenderer.renderWithStringBuilder(expression), + ) + + internal fun testMathML(mst: MST, expectedMathML: String) = assertEquals( + expected = "$expectedMathML", + actual = mathML(mst), + ) + + internal fun testMathML(expression: String, expectedMathML: String) = assertEquals( + expected = "$expectedMathML", + actual = mathML(expression.parseMath()), + ) + + internal fun testMathML(expression: MathSyntax, expectedMathML: String) = assertEquals( + expected = "$expectedMathML", + actual = MathMLSyntaxRenderer.renderWithStringBuilder(expression), + ) +} diff --git a/kmath-complex/README.md b/kmath-complex/README.md index d7b2937fd..ec5bf289f 100644 --- a/kmath-complex/README.md +++ b/kmath-complex/README.md @@ -8,7 +8,7 @@ Complex and hypercomplex number systems in KMath. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-complex:0.3.0-dev-3`. +The Maven coordinates of this project are `space.kscience:kmath-complex:0.3.0-dev-4`. **Gradle:** ```gradle @@ -19,7 +19,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-complex:0.3.0-dev-3' + implementation 'space.kscience:kmath-complex:0.3.0-dev-4' } ``` **Gradle Kotlin DSL:** @@ -31,6 +31,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-complex:0.3.0-dev-3") + implementation("space.kscience:kmath-complex:0.3.0-dev-4") } ``` diff --git a/kmath-core/README.md b/kmath-core/README.md index 096c7d833..5e4f1765d 100644 --- a/kmath-core/README.md +++ b/kmath-core/README.md @@ -15,7 +15,7 @@ performance calculations to code generation. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-core:0.3.0-dev-3`. +The Maven coordinates of this project are `space.kscience:kmath-core:0.3.0-dev-4`. **Gradle:** ```gradle @@ -26,7 +26,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-core:0.3.0-dev-3' + implementation 'space.kscience:kmath-core:0.3.0-dev-4' } ``` **Gradle Kotlin DSL:** @@ -38,6 +38,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-core:0.3.0-dev-3") + implementation("space.kscience:kmath-core:0.3.0-dev-4") } ``` diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index 18fbf0fdd..817bc9f9c 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -18,7 +18,8 @@ private typealias TBase = ULong /** * Kotlin Multiplatform implementation of Big Integer numbers (KBigInteger). * - * @author Robert Drynkin (https://github.com/robdrynkin) and Peter Klimai (https://github.com/pklimai) + * @author Robert Drynkin + * @author Peter Klimai */ @OptIn(UnstableKMathAPI::class) public object BigIntField : Field, NumbersAddOperations, ScaleOperations { diff --git a/kmath-ejml/README.md b/kmath-ejml/README.md index 2551703a4..1f13a03c5 100644 --- a/kmath-ejml/README.md +++ b/kmath-ejml/README.md @@ -9,7 +9,7 @@ EJML based linear algebra implementation. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-ejml:0.3.0-dev-3`. +The Maven coordinates of this project are `space.kscience:kmath-ejml:0.3.0-dev-4`. **Gradle:** ```gradle @@ -20,7 +20,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-ejml:0.3.0-dev-3' + implementation 'space.kscience:kmath-ejml:0.3.0-dev-4' } ``` **Gradle Kotlin DSL:** @@ -32,6 +32,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-ejml:0.3.0-dev-3") + implementation("space.kscience:kmath-ejml:0.3.0-dev-4") } ``` diff --git a/kmath-for-real/README.md b/kmath-for-real/README.md index ad3d33062..f9c6ed3a0 100644 --- a/kmath-for-real/README.md +++ b/kmath-for-real/README.md @@ -9,7 +9,7 @@ Specialization of KMath APIs for Double numbers. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-for-real:0.3.0-dev-3`. +The Maven coordinates of this project are `space.kscience:kmath-for-real:0.3.0-dev-4`. **Gradle:** ```gradle @@ -20,7 +20,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-for-real:0.3.0-dev-3' + implementation 'space.kscience:kmath-for-real:0.3.0-dev-4' } ``` **Gradle Kotlin DSL:** @@ -32,6 +32,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-for-real:0.3.0-dev-3") + implementation("space.kscience:kmath-for-real:0.3.0-dev-4") } ``` diff --git a/kmath-functions/README.md b/kmath-functions/README.md index 531e97a44..1e4b06e0f 100644 --- a/kmath-functions/README.md +++ b/kmath-functions/README.md @@ -10,7 +10,7 @@ Functions and interpolations. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-functions:0.3.0-dev-3`. +The Maven coordinates of this project are `space.kscience:kmath-functions:0.3.0-dev-4`. **Gradle:** ```gradle @@ -21,7 +21,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-functions:0.3.0-dev-3' + implementation 'space.kscience:kmath-functions:0.3.0-dev-4' } ``` **Gradle Kotlin DSL:** @@ -33,6 +33,6 @@ repositories { } dependencies { - implementation("space.kscience:kmath-functions:0.3.0-dev-3") + implementation("space.kscience:kmath-functions:0.3.0-dev-4") } ``` diff --git a/kmath-nd4j/README.md b/kmath-nd4j/README.md index 938d05c33..c8944f1ab 100644 --- a/kmath-nd4j/README.md +++ b/kmath-nd4j/README.md @@ -9,7 +9,7 @@ ND4J based implementations of KMath abstractions. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-nd4j:0.3.0-dev-3`. +The Maven coordinates of this project are `space.kscience:kmath-nd4j:0.3.0-dev-4`. **Gradle:** ```gradle @@ -20,7 +20,7 @@ repositories { } dependencies { - implementation 'space.kscience:kmath-nd4j:0.3.0-dev-3' + implementation 'space.kscience:kmath-nd4j:0.3.0-dev-4' } ``` **Gradle Kotlin DSL:** @@ -32,7 +32,7 @@ repositories { } dependencies { - implementation("space.kscience:kmath-nd4j:0.3.0-dev-3") + implementation("space.kscience:kmath-nd4j:0.3.0-dev-4") } ```