Redesign exponential function rendering #274

Merged
CommanderTvis merged 1 commits from commandertvis/exp-render into dev 2021-04-21 18:13:21 +03:00
16 changed files with 283 additions and 141 deletions

View File

@ -71,6 +71,15 @@ public object LatexSyntaxRenderer : SyntaxRenderer {
append('}')
}
is ExponentSyntax -> if (node.useOperatorForm) {
append("\\operatorname{exp}\\,")
render(node.operand)
} else {
append("e^{")
render(node.operand)
append('}')
}
is SuperscriptSyntax -> {
render(node.left)
append("^{")

View File

@ -82,6 +82,19 @@ public object MathMLSyntaxRenderer : SyntaxRenderer {
is RadicalSyntax -> tag("msqrt") { render(node.operand) }
is ExponentSyntax -> if (node.useOperatorForm) {
tag("mo") { append("exp") }
tag("mspace", "width" to "0.167em")
render(node.operand)
} else {
tag("msup") {
tag("mrow") {
tag("mi") { append("e") }
}
tag("mrow") { render(node.operand) }
}
}
is SuperscriptSyntax -> tag("msup") {
tag("mrow") { render(node.left) }
tag("mrow") { render(node.right) }

View File

@ -83,7 +83,7 @@ public open class FeaturedMathRendererWithPostProcess(
Fraction.Default,
Power.Default,
SquareRoot.Default,
Exponential.Default,
Exponent.Default,
InverseTrigonometricOperations.Default,
// Fallback option for unknown operations - printing them as operator
@ -100,6 +100,7 @@ public open class FeaturedMathRendererWithPostProcess(
PrintSymbolic,
),
listOf(
BetterExponent,
SimplifyParentheses.Default,
BetterMultiplication,
),

View File

@ -189,6 +189,24 @@ public data class RadicalSyntax(
}
}
/**
* Represents exponential function.
*
* @property operand The argument of function.
* @property useOperatorForm `true` if operator form is used (*exp (x)*), `false` if exponentiation form is used
* (*e<sup>x</sup>*).
* @author Iaroslav Postovalov
*/
public data class ExponentSyntax(
public override val operation: String,
public override val operand: OperandSyntax,
public var useOperatorForm: Boolean,
) : UnarySyntax() {
init {
operand.parent = this
}
}
/**
* Represents a syntax node with superscript (usually, for exponentiation).
*

View File

@ -56,10 +56,14 @@ private fun printSignedNumberString(s: String): MathSyntax {
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")
val toString = when (val v = node.value) {
is Float -> v.multiplatformToString()
is Double -> v.multiplatformToString()
else -> v.toString()
}.removeSuffix(".0")
if ('E' in toString) {
val (beforeE, afterE) = toString.split('E')
if (toString.contains('E', ignoreCase = true)) {
val (beforeE, afterE) = toString.split('E', ignoreCase = true)
val significand = beforeE.toDouble().toString().removeSuffix(".0")
val exponent = afterE.toDouble().toString().removeSuffix(".0")
@ -108,9 +112,7 @@ public class PrettyPrintFloats(public val types: Set<KClass<out Number>>) : Rend
*/
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
if (node !is MST.Numeric || node.value::class !in types) return null
return printSignedNumberString(node.value.toString())
}
@ -282,15 +284,15 @@ public class SquareRoot(operations: Collection<String>?) : Unary(operations) {
}
}
public class Exponential(operations: Collection<String>?) : Unary(operations) {
public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = SuperscriptSyntax(
public class Exponent(operations: Collection<String>?) : Unary(operations) {
public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = ExponentSyntax(
operation = node.operation,
left = SymbolSyntax(string = "e"),
right = parent.render(node.value),
operand = OperandSyntax(operand = parent.render(node.value), parentheses = true),
useOperatorForm = true,
)
public companion object {
public val Default: Exponential = Exponential(setOf(ExponentialOperations.EXP_OPERATION))
public val Default: Exponent = Exponent(setOf(ExponentialOperations.EXP_OPERATION))
}
}

View File

@ -0,0 +1,9 @@
/*
* Copyright 2018-2021 KMath contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscience.kmath.ast.rendering
internal expect fun Double.multiplatformToString(): String
internal expect fun Float.multiplatformToString(): String

View File

@ -16,69 +16,110 @@ import space.kscience.kmath.operations.RingOperations
* @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)
public override fun perform(node: MathSyntax): Unit = 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)
}
is UnaryOperatorSyntax -> {
perform(node.prefix)
perform(node.operand)
}
is UnaryPlusSyntax -> perform(node.operand)
is UnaryMinusSyntax -> perform(node.operand)
is RadicalSyntax -> perform(node.operand)
is ExponentSyntax -> 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)
}
}
}
/**
* Applies [ExponentSyntax.useOperatorForm] to [ExponentSyntax] when the operand contains a fraction, a
* superscript or a subscript to improve readability.
*
* @author Iaroslav Postovalov
*/
public object BetterExponent : FeaturedMathRendererWithPostProcess.PostProcessStage {
private fun perform0(node: MathSyntax): Boolean {
return when (node) {
is NumberSyntax -> false
is SymbolSyntax -> false
is OperatorNameSyntax -> false
is SpecialSymbolSyntax -> false
is OperandSyntax -> perform0(node.operand)
is UnaryOperatorSyntax -> perform0(node.prefix) || perform0(node.operand)
is UnaryPlusSyntax -> perform0(node.operand)
is UnaryMinusSyntax -> perform0(node.operand)
is RadicalSyntax -> perform0(node.operand)
is ExponentSyntax -> {
val r = perform0(node.operand)
node.useOperatorForm = r
r
}
is SuperscriptSyntax -> true
is SubscriptSyntax -> true
is BinaryOperatorSyntax -> perform0(node.prefix) || perform0(node.left) || perform0(node.right)
is BinaryPlusSyntax -> perform0(node.left) || perform0(node.right)
is BinaryMinusSyntax -> perform0(node.left) || perform0(node.right)
is FractionSyntax -> true
is RadicalWithIndexSyntax -> perform0(node.left) || perform0(node.right)
is MultiplicationSyntax -> perform0(node.left) || perform0(node.right)
}
}
public override fun perform(node: MathSyntax) {
perform0(node)
}
}
@ -90,89 +131,89 @@ public object BetterMultiplication : FeaturedMathRendererWithPostProcess.PostPro
*/
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
public override fun perform(node: MathSyntax): Unit = 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
is OperandSyntax -> {
val isRightOfSuperscript =
(node.parent is SuperscriptSyntax) && (node.parent as SuperscriptSyntax).right === node
val precedence = precedenceFunction(node.operand)
val precedence = precedenceFunction(node.operand)
val needParenthesesByPrecedence = when (val parent = node.parent) {
null -> false
val needParenthesesByPrecedence = when (val parent = node.parent) {
null -> false
is BinarySyntax -> {
val parentPrecedence = precedenceFunction(parent)
is BinarySyntax -> {
val parentPrecedence = precedenceFunction(parent)
parentPrecedence < precedence ||
parentPrecedence == precedence && parentPrecedence != 0 && node === parent.right
}
else -> precedence > precedenceFunction(parent)
parentPrecedence < precedence ||
parentPrecedence == precedence && parentPrecedence != 0 && node === parent.right
}
node.parentheses = !isRightOfSuperscript
&& (needParenthesesByPrecedence || node.parent is UnaryOperatorSyntax)
perform(node.operand)
else -> precedence > precedenceFunction(parent)
}
is UnaryOperatorSyntax -> {
perform(node.prefix)
perform(node.operand)
}
val isInsideExpOperator =
node.parent is ExponentSyntax && (node.parent as ExponentSyntax).useOperatorForm
is UnaryPlusSyntax -> perform(node.operand)
is UnaryMinusSyntax -> {
perform(node.operand)
}
is RadicalSyntax -> perform(node.operand)
node.parentheses = !isRightOfSuperscript
&& (needParenthesesByPrecedence || node.parent is UnaryOperatorSyntax || isInsideExpOperator)
is SuperscriptSyntax -> {
perform(node.left)
perform(node.right)
}
perform(node.operand)
}
is SubscriptSyntax -> {
perform(node.left)
perform(node.right)
}
is UnaryOperatorSyntax -> {
perform(node.prefix)
perform(node.operand)
}
is BinaryOperatorSyntax -> {
perform(node.prefix)
perform(node.left)
perform(node.right)
}
is UnaryPlusSyntax -> perform(node.operand)
is UnaryMinusSyntax -> perform(node.operand)
is RadicalSyntax -> perform(node.operand)
is ExponentSyntax -> perform(node.operand)
is BinaryPlusSyntax -> {
perform(node.left)
perform(node.right)
}
is SuperscriptSyntax -> {
perform(node.left)
perform(node.right)
}
is BinaryMinusSyntax -> {
perform(node.left)
perform(node.right)
}
is SubscriptSyntax -> {
perform(node.left)
perform(node.right)
}
is FractionSyntax -> {
perform(node.left)
perform(node.right)
}
is BinaryOperatorSyntax -> {
perform(node.prefix)
perform(node.left)
perform(node.right)
}
is MultiplicationSyntax -> {
perform(node.left)
perform(node.right)
}
is BinaryPlusSyntax -> {
perform(node.left)
perform(node.right)
}
is RadicalWithIndexSyntax -> {
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)
}
}

View File

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscisnce.kmath.ast
package space.kscience.kmath.ast
import space.kscience.kmath.ast.parseMath
import space.kscience.kmath.expressions.evaluate

View File

@ -3,9 +3,8 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscisnce.kmath.ast
package space.kscience.kmath.ast
import space.kscience.kmath.ast.parseMath
import space.kscience.kmath.complex.Complex
import space.kscience.kmath.complex.ComplexField
import space.kscience.kmath.expressions.evaluate

View File

@ -42,6 +42,22 @@ internal class TestFeatures {
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}")
testLatex(Numeric(0.001), "0.001")
testLatex(Numeric(0.0000001), "1\\times10^{-7}")
testLatex(Numeric(Float.NaN), "NaN")
testLatex(Numeric(Float.POSITIVE_INFINITY), "\\infty")
testLatex(Numeric(Float.NEGATIVE_INFINITY), "-\\infty")
testLatex(Numeric(1.0f), "1")
testLatex(Numeric(-1.0f), "-1")
testLatex(Numeric(1.42f), "1.42")
testLatex(Numeric(-1.42f), "-1.42")
testLatex(Numeric(1e10f), "1\\times10^{10}")
testLatex(Numeric(1e-10f), "1\\times10^{-10}")
testLatex(Numeric(-1e-10f), "-1\\times10^{-10}")
testLatex(Numeric(-1e10f), "-1\\times10^{10}")
testLatex(Numeric(0.001f), "0.001")
testLatex(Numeric(0.0000001f), "1\\times10^{-7}")
}
@Test

View File

@ -30,4 +30,11 @@ internal class TestStages {
testLatex("(x+x)^x+x*x", "\\left(x+x\\right)^{x}+x\\,x")
testLatex("x^(x+x)", "x^{x+x}")
}
@Test
fun exponent() {
testLatex("exp(x)", "e^{x}")
testLatex("exp(x/2)", "\\operatorname{exp}\\,\\left(\\frac{x}{2}\\right)")
testLatex("exp(x^2)", "\\operatorname{exp}\\,\\left(x^{2}\\right)")
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright 2018-2021 KMath contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscience.kmath.ast.rendering
internal actual fun Double.multiplatformToString(): String {
val d = this
if (d >= 1e7 || d <= -1e7) return js("d.toExponential()") as String
return toString()
}
internal actual fun Float.multiplatformToString(): String {
val d = this
if (d >= 1e7f || d <= -1e7f) return js("d.toExponential()") as String
return toString()
}

View File

@ -0,0 +1,9 @@
/*
* Copyright 2018-2021 KMath contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscience.kmath.ast.rendering
internal actual fun Double.multiplatformToString(): String = toString()
internal actual fun Float.multiplatformToString(): String = toString()