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('}') append('}')
} }
is ExponentSyntax -> if (node.useOperatorForm) {
append("\\operatorname{exp}\\,")
render(node.operand)
} else {
append("e^{")
render(node.operand)
append('}')
}
is SuperscriptSyntax -> { is SuperscriptSyntax -> {
render(node.left) render(node.left)
append("^{") append("^{")

View File

@ -82,6 +82,19 @@ public object MathMLSyntaxRenderer : SyntaxRenderer {
is RadicalSyntax -> tag("msqrt") { render(node.operand) } 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") { is SuperscriptSyntax -> tag("msup") {
tag("mrow") { render(node.left) } tag("mrow") { render(node.left) }
tag("mrow") { render(node.right) } tag("mrow") { render(node.right) }

View File

@ -83,7 +83,7 @@ public open class FeaturedMathRendererWithPostProcess(
Fraction.Default, Fraction.Default,
Power.Default, Power.Default,
SquareRoot.Default, SquareRoot.Default,
Exponential.Default, Exponent.Default,
InverseTrigonometricOperations.Default, InverseTrigonometricOperations.Default,
// Fallback option for unknown operations - printing them as operator // Fallback option for unknown operations - printing them as operator
@ -100,6 +100,7 @@ public open class FeaturedMathRendererWithPostProcess(
PrintSymbolic, PrintSymbolic,
), ),
listOf( listOf(
BetterExponent,
SimplifyParentheses.Default, SimplifyParentheses.Default,
BetterMultiplication, 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). * 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 class PrettyPrintFloats(public val types: Set<KClass<out Number>>) : RenderFeature {
public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { 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
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) { if (toString.contains('E', ignoreCase = true)) {
val (beforeE, afterE) = toString.split('E') val (beforeE, afterE) = toString.split('E', ignoreCase = true)
val significand = beforeE.toDouble().toString().removeSuffix(".0") val significand = beforeE.toDouble().toString().removeSuffix(".0")
val exponent = afterE.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 class PrettyPrintIntegers(public val types: Set<KClass<out Number>>) : RenderFeature {
public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? {
if (node !is MST.Numeric || node.value::class !in types) if (node !is MST.Numeric || node.value::class !in types) return null
return null
return printSignedNumberString(node.value.toString()) 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 class Exponent(operations: Collection<String>?) : Unary(operations) {
public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = SuperscriptSyntax( public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = ExponentSyntax(
operation = node.operation, operation = node.operation,
left = SymbolSyntax(string = "e"), operand = OperandSyntax(operand = parent.render(node.value), parentheses = true),
right = parent.render(node.value), useOperatorForm = true,
) )
public companion object { 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 * @author Iaroslav Postovalov
*/ */
public object BetterMultiplication : FeaturedMathRendererWithPostProcess.PostProcessStage { public object BetterMultiplication : FeaturedMathRendererWithPostProcess.PostProcessStage {
public override fun perform(node: MathSyntax) { public override fun perform(node: MathSyntax): Unit = when (node) {
when (node) { is NumberSyntax -> Unit
is NumberSyntax -> Unit is SymbolSyntax -> Unit
is SymbolSyntax -> Unit is OperatorNameSyntax -> Unit
is OperatorNameSyntax -> Unit is SpecialSymbolSyntax -> Unit
is SpecialSymbolSyntax -> Unit is OperandSyntax -> perform(node.operand)
is OperandSyntax -> perform(node.operand)
is UnaryOperatorSyntax -> { is UnaryOperatorSyntax -> {
perform(node.prefix) perform(node.prefix)
perform(node.operand) 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 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) : public class SimplifyParentheses(public val precedenceFunction: (MathSyntax) -> Int) :
FeaturedMathRendererWithPostProcess.PostProcessStage { FeaturedMathRendererWithPostProcess.PostProcessStage {
public override fun perform(node: MathSyntax) { public override fun perform(node: MathSyntax): Unit = when (node) {
when (node) { is NumberSyntax -> Unit
is NumberSyntax -> Unit is SymbolSyntax -> Unit
is SymbolSyntax -> Unit is OperatorNameSyntax -> Unit
is OperatorNameSyntax -> Unit is SpecialSymbolSyntax -> Unit
is SpecialSymbolSyntax -> Unit
is OperandSyntax -> { is OperandSyntax -> {
val isRightOfSuperscript = val isRightOfSuperscript =
(node.parent is SuperscriptSyntax) && (node.parent as SuperscriptSyntax).right === node (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) { val needParenthesesByPrecedence = when (val parent = node.parent) {
null -> false null -> false
is BinarySyntax -> { is BinarySyntax -> {
val parentPrecedence = precedenceFunction(parent) val parentPrecedence = precedenceFunction(parent)
parentPrecedence < precedence || parentPrecedence < precedence ||
parentPrecedence == precedence && parentPrecedence != 0 && node === parent.right parentPrecedence == precedence && parentPrecedence != 0 && node === parent.right
}
else -> precedence > precedenceFunction(parent)
} }
node.parentheses = !isRightOfSuperscript else -> precedence > precedenceFunction(parent)
&& (needParenthesesByPrecedence || node.parent is UnaryOperatorSyntax)
perform(node.operand)
} }
is UnaryOperatorSyntax -> { val isInsideExpOperator =
perform(node.prefix) node.parent is ExponentSyntax && (node.parent as ExponentSyntax).useOperatorForm
perform(node.operand)
}
is UnaryPlusSyntax -> perform(node.operand) node.parentheses = !isRightOfSuperscript
is UnaryMinusSyntax -> { && (needParenthesesByPrecedence || node.parent is UnaryOperatorSyntax || isInsideExpOperator)
perform(node.operand)
}
is RadicalSyntax -> perform(node.operand)
is SuperscriptSyntax -> { perform(node.operand)
perform(node.left) }
perform(node.right)
}
is SubscriptSyntax -> { is UnaryOperatorSyntax -> {
perform(node.left) perform(node.prefix)
perform(node.right) perform(node.operand)
} }
is BinaryOperatorSyntax -> { is UnaryPlusSyntax -> perform(node.operand)
perform(node.prefix) is UnaryMinusSyntax -> perform(node.operand)
perform(node.left) is RadicalSyntax -> perform(node.operand)
perform(node.right) is ExponentSyntax -> perform(node.operand)
}
is BinaryPlusSyntax -> { is SuperscriptSyntax -> {
perform(node.left) perform(node.left)
perform(node.right) perform(node.right)
} }
is BinaryMinusSyntax -> { is SubscriptSyntax -> {
perform(node.left) perform(node.left)
perform(node.right) perform(node.right)
} }
is FractionSyntax -> { is BinaryOperatorSyntax -> {
perform(node.left) perform(node.prefix)
perform(node.right) perform(node.left)
} perform(node.right)
}
is MultiplicationSyntax -> { is BinaryPlusSyntax -> {
perform(node.left) perform(node.left)
perform(node.right) perform(node.right)
} }
is RadicalWithIndexSyntax -> { is BinaryMinusSyntax -> {
perform(node.left) perform(node.left)
perform(node.right) 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. * 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.ast.parseMath
import space.kscience.kmath.expressions.evaluate 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. * 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.Complex
import space.kscience.kmath.complex.ComplexField import space.kscience.kmath.complex.ComplexField
import space.kscience.kmath.expressions.evaluate 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.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(-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 @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", "\\left(x+x\\right)^{x}+x\\,x")
testLatex("x^(x+x)", "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()