Merge pull request #264 from mipt-npm/commandertvis/ast-rendering

Some experiments with MST rendering
This commit is contained in:
Alexander Nozik 2021-03-31 13:06:14 +03:00 committed by GitHub
commit ae911fcc2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1655 additions and 29 deletions

View File

@ -259,8 +259,8 @@ repositories {
} }
dependencies { dependencies {
api("space.kscience:kmath-core:0.3.0-dev-3") api("space.kscience:kmath-core:0.3.0-dev-4")
// api("kscience.kmath:kmath-core-jvm:0.3.0-dev-3") for jvm-specific version // api("kscience.kmath:kmath-core-jvm:0.3.0-dev-4") for jvm-specific version
} }
``` ```

View File

@ -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)
}

View File

@ -12,7 +12,7 @@ Abstract syntax tree expression representation and related optimizations.
## Artifact: ## 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:**
```gradle ```gradle
@ -23,7 +23,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'space.kscience:kmath-ast:0.3.0-dev-3' implementation 'space.kscience:kmath-ast:0.3.0-dev-4'
} }
``` ```
**Gradle Kotlin DSL:** **Gradle Kotlin DSL:**
@ -35,7 +35,7 @@ repositories {
} }
dependencies { 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 #### Known issues
- This feature uses `eval` which can be unavailable in several environments. - 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
<mrow><msup><mrow><mi>e</mi></mrow><mrow><msqrt><mi>x</mi></msqrt></mrow></msup><mo>-</mo><mfrac><mrow><mfrac><mrow><msup><mrow><mo>sin</mo></mrow><mrow><mo>-</mo><mn>1</mn></mrow></msup><mspace width="0.167em"></mspace><mfenced open="(" close=")" separators=""><mn>2</mn><mspace width="0.167em"></mspace><mi>x</mi></mfenced></mrow><mrow><mn>2</mn><mo>&times;</mo><msup><mrow><mn>10</mn></mrow><mrow><mn>10</mn></mrow></msup><mo>+</mo><msup><mrow><mi>x</mi></mrow><mrow><mn>3</mn></mrow></msup></mrow></mfrac></mrow><mrow><mo>-</mo><mn>12</mn></mrow></mfrac></mrow>
```
It is also possible to create custom algorithms of render, and even add support of other markup languages
(see API reference).

View File

@ -78,3 +78,39 @@ var executable = function (constants, arguments) {
#### Known issues #### Known issues
- This feature uses `eval` which can be unavailable in several environments. - 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
<mrow><msup><mrow><mi>e</mi></mrow><mrow><msqrt><mi>x</mi></msqrt></mrow></msup><mo>-</mo><mfrac><mrow><mfrac><mrow><msup><mrow><mo>sin</mo></mrow><mrow><mo>-</mo><mn>1</mn></mrow></msup><mspace width="0.167em"></mspace><mfenced open="(" close=")" separators=""><mn>2</mn><mspace width="0.167em"></mspace><mi>x</mi></mfenced></mrow><mrow><mn>2</mn><mo>&times;</mo><msup><mrow><mn>10</mn></mrow><mrow><mn>10</mn></mrow></msup><mo>+</mo><msup><mrow><mi>x</mi></mrow><mrow><mn>3</mn></mrow></msup></mrow></mfrac></mrow><mrow><mo>-</mo><mn>12</mn></mrow></mfrac></mrow>
```
It is also possible to create custom algorithms of render, and even add support of other markup languages
(see API reference).

View File

@ -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)
}
}
}
}

View File

@ -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("<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mrow>")
render0(node, output)
output.append("</mrow></math>")
}
private fun render0(node: MathSyntax, output: Appendable): Unit = output.run {
fun tag(tagName: String, vararg attr: Pair<String, String>, 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("</")
append(tagName)
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("&infin;") }
}
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("&times;") } else tag("mspace", "width" to "0.167em")
render(node.right)
}
}
}
}

View File

@ -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<RenderFeature>) : 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<RenderFeature>,
public val stages: List<PostProcessStage>,
) : 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,
)
)
}
}

View File

@ -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 (&infin;) 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&times;2).
*
* @property left The multiplicand.
* @property right The multiplier.
* @property times whether the times (&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
}
}

View File

@ -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()
}

View File

@ -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<KClass<out Number>>) : 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<KClass<out Number>>) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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<String>?) : 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,
))
}
}

View File

@ -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 (&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
}
}
}
}

View File

@ -68,7 +68,7 @@ internal fun <T> MST.compileWith(algebra: Algebra<T>): Expression<T> {
/** /**
* Compiles an [MST] to ESTree generated expression using given algebra. * Compiles an [MST] to ESTree generated expression using given algebra.
* *
* @author Alexander Nozik. * @author Iaroslav Postovalov
*/ */
public fun <T : Any> Algebra<T>.expression(mst: MST): Expression<T> = public fun <T : Any> Algebra<T>.expression(mst: MST): Expression<T> =
mst.compileWith(this) mst.compileWith(this)
@ -76,7 +76,7 @@ public fun <T : Any> Algebra<T>.expression(mst: MST): Expression<T> =
/** /**
* Optimizes performance of an [MstExpression] by compiling it into ESTree generated expression. * Optimizes performance of an [MstExpression] by compiling it into ESTree generated expression.
* *
* @author Alexander Nozik. * @author Iaroslav Postovalov
*/ */
public fun <T : Any> MstExpression<T, Algebra<T>>.compile(): Expression<T> = public fun <T : Any> MstExpression<T, Algebra<T>>.compile(): Expression<T> =
mst.compileWith(algebra) mst.compileWith(algebra)

View File

@ -73,7 +73,7 @@ internal fun <T : Any> MST.compileWith(type: Class<T>, algebra: Algebra<T>): Exp
/** /**
* Compiles an [MST] to ASM using given algebra. * Compiles an [MST] to ASM using given algebra.
* *
* @author Alexander Nozik. * @author Alexander Nozik
*/ */
public inline fun <reified T : Any> Algebra<T>.expression(mst: MST): Expression<T> = public inline fun <reified T : Any> Algebra<T>.expression(mst: MST): Expression<T> =
mst.compileWith(T::class.java, this) mst.compileWith(T::class.java, this)
@ -81,7 +81,7 @@ public inline fun <reified T : Any> Algebra<T>.expression(mst: MST): Expression<
/** /**
* Optimizes performance of an [MstExpression] using ASM codegen. * Optimizes performance of an [MstExpression] using ASM codegen.
* *
* @author Alexander Nozik. * @author Alexander Nozik
*/ */
public inline fun <reified T : Any> MstExpression<T, Algebra<T>>.compile(): Expression<T> = public inline fun <reified T : Any> MstExpression<T, Algebra<T>>.compile(): Expression<T> =
mst.compileWith(T::class.java, algebra) mst.compileWith(T::class.java, algebra)

View File

@ -21,7 +21,8 @@ import space.kscience.kmath.operations.RingOperations
/** /**
* better-parse implementation of grammar defined in the ArithmeticsEvaluator.g4. * 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<MST>() { public object ArithmeticsEvaluator : Grammar<MST>() {
// TODO replace with "...".toRegex() when better-parse 0.4.1 is released // TODO replace with "...".toRegex() when better-parse 0.4.1 is released

View File

@ -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")
// }
}

View File

@ -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")
}
}

View File

@ -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", "<mn>42</mn>")
@Test
fun symbol() = testMathML("x", "<mi>x</mi>")
@Test
fun operatorName() = testMathML(
"sin(1)",
"<mo>sin</mo><mspace width=\"0.167em\"></mspace><mfenced open=\"(\" close=\")\" separators=\"\"><mn>1</mn></mfenced>",
)
@Test
fun specialSymbol() = testMathML(MST.Numeric(Double.POSITIVE_INFINITY), "<mo>&infin;</mo>")
@Test
fun operand() {
testMathML(
"sin(1)",
"<mo>sin</mo><mspace width=\"0.167em\"></mspace><mfenced open=\"(\" close=\")\" separators=\"\"><mn>1</mn></mfenced>",
)
testMathML("1+1", "<mn>1</mn><mo>+</mo><mn>1</mn>")
}
@Test
fun unaryOperator() = testMathML(
"sin(1)",
"<mo>sin</mo><mspace width=\"0.167em\"></mspace><mfenced open=\"(\" close=\")\" separators=\"\"><mn>1</mn></mfenced>",
)
@Test
fun unaryPlus() =
testMathML(MST.Unary(GroupOperations.PLUS_OPERATION, MST.Numeric(1)), "<mo>+</mo><mn>1</mn>")
@Test
fun unaryMinus() = testMathML("-x", "<mo>-</mo><mi>x</mi>")
@Test
fun radical() = testMathML("sqrt(x)", "<msqrt><mi>x</mi></msqrt>")
@Test
fun superscript() = testMathML("x^y", "<msup><mrow><mi>x</mi></mrow><mrow><mi>y</mi></mrow></msup>")
@Test
fun subscript() = testMathML(
SubscriptSyntax("", SymbolSyntax("x"), NumberSyntax("123")),
"<msub><mrow><mi>x</mi></mrow><mrow><mn>123</mn></mrow></msub>",
)
@Test
fun binaryOperator() = testMathML(
"f(x, y)",
"<mo>f</mo><mfenced open=\"(\" close=\")\" separators=\"\"><mi>x</mi><mo>,</mo><mi>y</mi></mfenced>",
)
@Test
fun binaryPlus() = testMathML("x+x", "<mi>x</mi><mo>+</mo><mi>x</mi>")
@Test
fun binaryMinus() = testMathML("x-x", "<mi>x</mi><mo>-</mo><mi>x</mi>")
@Test
fun fraction() = testMathML("x/x", "<mfrac><mrow><mi>x</mi></mrow><mrow><mi>x</mi></mrow></mfrac>")
@Test
fun radicalWithIndex() =
testMathML(RadicalWithIndexSyntax("", SymbolSyntax("x"), SymbolSyntax("y")),
"<mroot><mrow><mi>y</mi></mrow><mrow><mi>x</mi></mrow></mroot>")
@Test
fun multiplication() {
testMathML("x*1", "<mi>x</mi><mo>&times;</mo><mn>1</mn>")
testMathML("1*x", "<mn>1</mn><mspace width=\"0.167em\"></mspace><mi>x</mi>")
}
}

View File

@ -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}")
}
}

View File

@ -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 = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mrow>$expectedMathML</mrow></math>",
actual = mathML(mst),
)
internal fun testMathML(expression: String, expectedMathML: String) = assertEquals(
expected = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mrow>$expectedMathML</mrow></math>",
actual = mathML(expression.parseMath()),
)
internal fun testMathML(expression: MathSyntax, expectedMathML: String) = assertEquals(
expected = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mrow>$expectedMathML</mrow></math>",
actual = MathMLSyntaxRenderer.renderWithStringBuilder(expression),
)
}

View File

@ -8,7 +8,7 @@ Complex and hypercomplex number systems in KMath.
## Artifact: ## 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:**
```gradle ```gradle
@ -19,7 +19,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'space.kscience:kmath-complex:0.3.0-dev-3' implementation 'space.kscience:kmath-complex:0.3.0-dev-4'
} }
``` ```
**Gradle Kotlin DSL:** **Gradle Kotlin DSL:**
@ -31,6 +31,6 @@ repositories {
} }
dependencies { dependencies {
implementation("space.kscience:kmath-complex:0.3.0-dev-3") implementation("space.kscience:kmath-complex:0.3.0-dev-4")
} }
``` ```

View File

@ -15,7 +15,7 @@ performance calculations to code generation.
## Artifact: ## 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:**
```gradle ```gradle
@ -26,7 +26,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'space.kscience:kmath-core:0.3.0-dev-3' implementation 'space.kscience:kmath-core:0.3.0-dev-4'
} }
``` ```
**Gradle Kotlin DSL:** **Gradle Kotlin DSL:**
@ -38,6 +38,6 @@ repositories {
} }
dependencies { dependencies {
implementation("space.kscience:kmath-core:0.3.0-dev-3") implementation("space.kscience:kmath-core:0.3.0-dev-4")
} }
``` ```

View File

@ -18,7 +18,8 @@ private typealias TBase = ULong
/** /**
* Kotlin Multiplatform implementation of Big Integer numbers (KBigInteger). * 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) @OptIn(UnstableKMathAPI::class)
public object BigIntField : Field<BigInt>, NumbersAddOperations<BigInt>, ScaleOperations<BigInt> { public object BigIntField : Field<BigInt>, NumbersAddOperations<BigInt>, ScaleOperations<BigInt> {

View File

@ -9,7 +9,7 @@ EJML based linear algebra implementation.
## Artifact: ## 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:**
```gradle ```gradle
@ -20,7 +20,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'space.kscience:kmath-ejml:0.3.0-dev-3' implementation 'space.kscience:kmath-ejml:0.3.0-dev-4'
} }
``` ```
**Gradle Kotlin DSL:** **Gradle Kotlin DSL:**
@ -32,6 +32,6 @@ repositories {
} }
dependencies { dependencies {
implementation("space.kscience:kmath-ejml:0.3.0-dev-3") implementation("space.kscience:kmath-ejml:0.3.0-dev-4")
} }
``` ```

View File

@ -9,7 +9,7 @@ Specialization of KMath APIs for Double numbers.
## Artifact: ## 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:**
```gradle ```gradle
@ -20,7 +20,7 @@ repositories {
} }
dependencies { 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:** **Gradle Kotlin DSL:**
@ -32,6 +32,6 @@ repositories {
} }
dependencies { dependencies {
implementation("space.kscience:kmath-for-real:0.3.0-dev-3") implementation("space.kscience:kmath-for-real:0.3.0-dev-4")
} }
``` ```

View File

@ -10,7 +10,7 @@ Functions and interpolations.
## Artifact: ## 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:**
```gradle ```gradle
@ -21,7 +21,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'space.kscience:kmath-functions:0.3.0-dev-3' implementation 'space.kscience:kmath-functions:0.3.0-dev-4'
} }
``` ```
**Gradle Kotlin DSL:** **Gradle Kotlin DSL:**
@ -33,6 +33,6 @@ repositories {
} }
dependencies { dependencies {
implementation("space.kscience:kmath-functions:0.3.0-dev-3") implementation("space.kscience:kmath-functions:0.3.0-dev-4")
} }
``` ```

View File

@ -9,7 +9,7 @@ ND4J based implementations of KMath abstractions.
## Artifact: ## 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:**
```gradle ```gradle
@ -20,7 +20,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'space.kscience:kmath-nd4j:0.3.0-dev-3' implementation 'space.kscience:kmath-nd4j:0.3.0-dev-4'
} }
``` ```
**Gradle Kotlin DSL:** **Gradle Kotlin DSL:**
@ -32,7 +32,7 @@ repositories {
} }
dependencies { dependencies {
implementation("space.kscience:kmath-nd4j:0.3.0-dev-3") implementation("space.kscience:kmath-nd4j:0.3.0-dev-4")
} }
``` ```