Improved trigonometry operations #99

Closed
CommanderTvis wants to merge 22 commits from adv-expr-improved-trigonometry into adv-expr
9 changed files with 164 additions and 70 deletions

22
docs/codestyle.md Normal file
View File

@ -0,0 +1,22 @@
# Local coding conventions
Kmath and other `scientifik` projects use general [kotlin code conventions](https://kotlinlang.org/docs/reference/coding-conventions.html), but with a number of small changes and clarifications.
## Utility class names
File name should coincide with a name of one of the classes contained in the file or start with small letter and describe its contents.
The code convention [here](https://kotlinlang.org/docs/reference/coding-conventions.html#source-file-names) says that file names should start with capital letter even if file does not contain classes. Yet starting utility classes and aggregators with a small letter seems to be a good way to clearly visually separate those files.
This convention could be changed in future in a non-breaking way.
## Private variable names
Private variable names could start with underscore `_` in case the private mutable variable is shadowed by the public read-only value with the same meaning.
Code convention do not permit underscores in names, but is is sometimes useful to "underscore" the fact that public and private versions define the same entity. It is allowed only for private variables.
This convention could be changed in future in a non-breaking way.
## Functions and properties one-liners
Use one-liners when they occupy single code window line both for functions and properties with getters like `val b: String get() = "fff"`. The same should be done with multiline expressions when they could be cleanly separated.
There is not general consensus whenever use `fun a() = {}` or `fun a(){return}`. Yet from reader perspective one-lines seem to better show that the property or function is easily calculated.

View File

@ -59,8 +59,10 @@ class DerivativeStructureField(
override fun divide(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.divide(b) override fun divide(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.divide(b)
override fun sin(arg: DerivativeStructure): DerivativeStructure = arg.sin() override fun sin(arg: DerivativeStructure): DerivativeStructure = arg.sin()
override fun cos(arg: DerivativeStructure): DerivativeStructure = arg.cos() override fun cos(arg: DerivativeStructure): DerivativeStructure = arg.cos()
override fun asin(arg: DerivativeStructure): DerivativeStructure = arg.asin()
override fun acos(arg: DerivativeStructure): DerivativeStructure = arg.acos()
override fun atan(arg: DerivativeStructure): DerivativeStructure = arg.atan()
override fun power(arg: DerivativeStructure, pow: Number): DerivativeStructure = when (pow) { override fun power(arg: DerivativeStructure, pow: Number): DerivativeStructure = when (pow) {
is Double -> arg.pow(pow) is Double -> arg.pow(pow)
@ -136,6 +138,3 @@ object DiffExpressionAlgebra : ExpressionAlgebra<Double, DiffExpression>, Field<
override fun divide(a: DiffExpression, b: DiffExpression) = override fun divide(a: DiffExpression, b: DiffExpression) =
DiffExpression { a.function(this) / b.function(this) } DiffExpression { a.function(this) / b.function(this) }
} }

View File

@ -8,6 +8,8 @@ import scientifik.memory.MemorySpec
import scientifik.memory.MemoryWriter import scientifik.memory.MemoryWriter
import kotlin.math.* import kotlin.math.*
private val PI_DIV_2 = Complex(PI / 2, 0)
/** /**
* A field for complex numbers * A field for complex numbers
*/ */
@ -30,9 +32,11 @@ object ComplexField : ExtendedField<Complex> {
return Complex((a.re * b.re + a.im * b.im) / norm, (a.re * b.im - a.im * b.re) / norm) return Complex((a.re * b.re + a.im * b.im) / norm, (a.re * b.im - a.im * b.re) / norm)
} }
override fun sin(arg: Complex): Complex = i / 2 * (exp(-i * arg) - exp(i * arg)) override fun sin(arg: Complex): Complex = i * (exp(-i * arg) - exp(i * arg)) / 2
override fun cos(arg: Complex): Complex = (exp(-i * arg) + exp(i * arg)) / 2 override fun cos(arg: Complex): Complex = (exp(-i * arg) + exp(i * arg)) / 2
override fun asin(arg: Complex): Complex = -i * ln(sqrt(one - arg pow 2) + i * arg)
override fun acos(arg: Complex): Complex = PI_DIV_2 + i * ln(sqrt(one - arg pow 2) + i * arg)
override fun atan(arg: Complex): Complex = i * (ln(one - i * arg) - ln(one + i * arg)) / 2
override fun power(arg: Complex, pow: Number): Complex = override fun power(arg: Complex, pow: Number): Complex =
arg.r.pow(pow.toDouble()) * (cos(pow.toDouble() * arg.theta) + i * sin(pow.toDouble() * arg.theta)) arg.r.pow(pow.toDouble()) * (cos(pow.toDouble() * arg.theta) + i * sin(pow.toDouble() * arg.theta))

View File

@ -8,7 +8,7 @@ import kotlin.math.pow as kpow
*/ */
interface ExtendedFieldOperations<T> : interface ExtendedFieldOperations<T> :
FieldOperations<T>, FieldOperations<T>,
TrigonometricOperations<T>, InverseTrigonometricOperations<T>,
PowerOperations<T>, PowerOperations<T>,
ExponentialOperations<T> { ExponentialOperations<T> {
@ -20,18 +20,15 @@ interface ExtendedFieldOperations<T> :
PowerOperations.SQRT_OPERATION -> sqrt(arg) PowerOperations.SQRT_OPERATION -> sqrt(arg)
ExponentialOperations.EXP_OPERATION -> exp(arg) ExponentialOperations.EXP_OPERATION -> exp(arg)
ExponentialOperations.LN_OPERATION -> ln(arg) ExponentialOperations.LN_OPERATION -> ln(arg)
else -> super.unaryOperation(operation, arg) else -> super<InverseTrigonometricOperations>.unaryOperation(operation, arg)
} }
} }
interface ExtendedField<T> : ExtendedFieldOperations<T>, Field<T> { interface ExtendedField<T> : ExtendedFieldOperations<T>, Field<T> {
override fun rightSideNumberOperation(operation: String, left: T, right: Number): T { override fun rightSideNumberOperation(operation: String, left: T, right: Number): T = when (operation) {
return when (operation) { PowerOperations.POW_OPERATION -> power(left, right)
PowerOperations.POW_OPERATION -> power(left, right) else -> super.rightSideNumberOperation(operation, left, right)
else -> super.rightSideNumberOperation(operation, left, right)
}
} }
} }
@ -65,7 +62,10 @@ object RealField : ExtendedField<Double>, Norm<Double, Double> {
override inline fun sin(arg: Double) = kotlin.math.sin(arg) override inline fun sin(arg: Double) = kotlin.math.sin(arg)
override inline fun cos(arg: Double) = kotlin.math.cos(arg) override inline fun cos(arg: Double) = kotlin.math.cos(arg)
override inline fun tan(arg: Double) = kotlin.math.tan(arg) override inline fun tan(arg: Double): Double = kotlin.math.tan(arg)
override inline fun acos(arg: Double): Double = kotlin.math.acos(arg)
override inline fun asin(arg: Double): Double = kotlin.math.asin(arg)
override inline fun atan(arg: Double): Double = kotlin.math.atan(arg)
override inline fun power(arg: Double, pow: Number) = arg.kpow(pow.toDouble()) override inline fun power(arg: Double, pow: Number) = arg.kpow(pow.toDouble())
@ -97,8 +97,10 @@ object FloatField : ExtendedField<Float>, Norm<Float, Float> {
override inline fun sin(arg: Float) = kotlin.math.sin(arg) override inline fun sin(arg: Float) = kotlin.math.sin(arg)
override inline fun cos(arg: Float) = kotlin.math.cos(arg) override inline fun cos(arg: Float) = kotlin.math.cos(arg)
override inline fun tan(arg: Float) = kotlin.math.tan(arg)
override inline fun tan(arg: Float): Float = kotlin.math.tan(arg) override inline fun acos(arg: Float) = kotlin.math.acos(arg)
override inline fun asin(arg: Float) = kotlin.math.asin(arg)
override inline fun atan(arg: Float) = kotlin.math.atan(arg)
override inline fun power(arg: Float, pow: Number) = arg.pow(pow.toFloat()) override inline fun power(arg: Float, pow: Number) = arg.pow(pow.toFloat())

View File

@ -10,28 +10,43 @@ package scientifik.kmath.operations
* It also allows to override behavior for optional operations * It also allows to override behavior for optional operations
* *
*/ */
interface TrigonometricOperations<T> { interface TrigonometricOperations<T> : FieldOperations<T> {
fun sin(arg: T): T fun sin(arg: T): T
fun cos(arg: T): T fun cos(arg: T): T
fun tan(arg: T): T = sin(arg) / cos(arg)
fun tan(arg: T): T
companion object { companion object {
const val SIN_OPERATION = "sin" const val SIN_OPERATION = "sin"
const val COS_OPERATION = "cos" const val COS_OPERATION = "cos"
const val TAN_OPERATION = "tan"
}
}
interface InverseTrigonometricOperations<T> : TrigonometricOperations<T> {
fun asin(arg: T): T
fun acos(arg: T): T
fun atan(arg: T): T
companion object {
const val ASIN_OPERATION = "asin"
const val ACOS_OPERATION = "acos"
const val ATAN_OPERATION = "atan"
} }
} }
fun <T : MathElement<out TrigonometricOperations<T>>> sin(arg: T): T = arg.context.sin(arg) fun <T : MathElement<out TrigonometricOperations<T>>> sin(arg: T): T = arg.context.sin(arg)
fun <T : MathElement<out TrigonometricOperations<T>>> cos(arg: T): T = arg.context.cos(arg) fun <T : MathElement<out TrigonometricOperations<T>>> cos(arg: T): T = arg.context.cos(arg)
fun <T : MathElement<out TrigonometricOperations<T>>> tan(arg: T): T = arg.context.tan(arg) fun <T : MathElement<out TrigonometricOperations<T>>> tan(arg: T): T = arg.context.tan(arg)
fun <T : MathElement<out InverseTrigonometricOperations<T>>> asin(arg: T): T = arg.context.asin(arg)
fun <T : MathElement<out InverseTrigonometricOperations<T>>> acos(arg: T): T = arg.context.acos(arg)
fun <T : MathElement<out InverseTrigonometricOperations<T>>> atan(arg: T): T = arg.context.atan(arg)
/* Power and roots */ /* Power and roots */
/** /**
* A context extension to include power operations like square roots, etc * A context extension to include power operations like square roots, etc
*/ */
interface PowerOperations<T> { interface PowerOperations<T> : Algebra<T> {
fun power(arg: T, pow: Number): T fun power(arg: T, pow: Number): T
fun sqrt(arg: T) = power(arg, 0.5) fun sqrt(arg: T) = power(arg, 0.5)
@ -49,7 +64,7 @@ fun <T : MathElement<out PowerOperations<T>>> sqr(arg: T): T = arg pow 2.0
/* Exponential */ /* Exponential */
interface ExponentialOperations<T> { interface ExponentialOperations<T>: Algebra<T> {
fun exp(arg: T): T fun exp(arg: T): T
fun ln(arg: T): T fun ln(arg: T): T

View File

@ -79,6 +79,13 @@ class ComplexNDField(override val shape: IntArray) :
override fun cos(arg: NDBuffer<Complex>) = map(arg) { cos(it) } override fun cos(arg: NDBuffer<Complex>) = map(arg) { cos(it) }
override fun tan(arg: NDBuffer<Complex>): NDBuffer<Complex> = map(arg) { tan(it) }
override fun asin(arg: NDBuffer<Complex>): NDBuffer<Complex> = map(arg) { asin(it) }
override fun acos(arg: NDBuffer<Complex>): NDBuffer<Complex> = map(arg) {acos(it)}
override fun atan(arg: NDBuffer<Complex>): NDBuffer<Complex> = map(arg) {atan(it)}
} }

View File

@ -9,8 +9,10 @@ import kotlin.math.*
* A simple field over linear buffers of [Double] * A simple field over linear buffers of [Double]
*/ */
object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> { object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
override fun add(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer { override fun add(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " } require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " }
return if (a is DoubleBuffer && b is DoubleBuffer) { return if (a is DoubleBuffer && b is DoubleBuffer) {
val aArray = a.array val aArray = a.array
val bArray = b.array val bArray = b.array
@ -22,6 +24,7 @@ object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
override fun multiply(a: Buffer<Double>, k: Number): DoubleBuffer { override fun multiply(a: Buffer<Double>, k: Number): DoubleBuffer {
val kValue = k.toDouble() val kValue = k.toDouble()
return if (a is DoubleBuffer) { return if (a is DoubleBuffer) {
val aArray = a.array val aArray = a.array
DoubleBuffer(DoubleArray(a.size) { aArray[it] * kValue }) DoubleBuffer(DoubleArray(a.size) { aArray[it] * kValue })
@ -32,6 +35,7 @@ object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
override fun multiply(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer { override fun multiply(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " } require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " }
return if (a is DoubleBuffer && b is DoubleBuffer) { return if (a is DoubleBuffer && b is DoubleBuffer) {
val aArray = a.array val aArray = a.array
val bArray = b.array val bArray = b.array
@ -43,6 +47,7 @@ object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
override fun divide(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer { override fun divide(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " } require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " }
return if (a is DoubleBuffer && b is DoubleBuffer) { return if (a is DoubleBuffer && b is DoubleBuffer) {
val aArray = a.array val aArray = a.array
val bArray = b.array val bArray = b.array
@ -52,49 +57,67 @@ object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
} }
} }
override fun sin(arg: Buffer<Double>): DoubleBuffer { override fun sin(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
return if (arg is DoubleBuffer) { val array = arg.array
val array = arg.array DoubleBuffer(DoubleArray(arg.size) { sin(array[it]) })
DoubleBuffer(DoubleArray(arg.size) { sin(array[it]) }) } else {
} else { DoubleBuffer(DoubleArray(arg.size) { sin(arg[it]) })
DoubleBuffer(DoubleArray(arg.size) { sin(arg[it]) })
}
} }
override fun cos(arg: Buffer<Double>): DoubleBuffer { override fun cos(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
return if (arg is DoubleBuffer) { val array = arg.array
val array = arg.array DoubleBuffer(DoubleArray(arg.size) { cos(array[it]) })
DoubleBuffer(DoubleArray(arg.size) { cos(array[it]) }) } else {
} else { DoubleBuffer(DoubleArray(arg.size) { cos(arg[it]) })
DoubleBuffer(DoubleArray(arg.size) { cos(arg[it]) })
}
} }
override fun power(arg: Buffer<Double>, pow: Number): DoubleBuffer { override fun tan(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
return if (arg is DoubleBuffer) { val array = arg.array
val array = arg.array DoubleBuffer(DoubleArray(arg.size) { tan(array[it]) })
DoubleBuffer(DoubleArray(arg.size) { array[it].pow(pow.toDouble()) }) } else {
} else { DoubleBuffer(DoubleArray(arg.size) { tan(arg[it]) })
DoubleBuffer(DoubleArray(arg.size) { arg[it].pow(pow.toDouble()) })
}
} }
override fun exp(arg: Buffer<Double>): DoubleBuffer { override fun asin(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
return if (arg is DoubleBuffer) { val array = arg.array
val array = arg.array DoubleBuffer(DoubleArray(arg.size) { asin(array[it]) })
DoubleBuffer(DoubleArray(arg.size) { exp(array[it]) }) } else {
} else { DoubleBuffer(DoubleArray(arg.size) { asin(arg[it]) })
DoubleBuffer(DoubleArray(arg.size) { exp(arg[it]) })
}
} }
override fun ln(arg: Buffer<Double>): DoubleBuffer { override fun acos(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
return if (arg is DoubleBuffer) { val array = arg.array
val array = arg.array DoubleBuffer(DoubleArray(arg.size) { acos(array[it]) })
DoubleBuffer(DoubleArray(arg.size) { ln(array[it]) }) } else {
} else { DoubleBuffer(DoubleArray(arg.size) { acos(arg[it]) })
DoubleBuffer(DoubleArray(arg.size) { ln(arg[it]) }) }
}
override fun atan(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
val array = arg.array
DoubleBuffer(DoubleArray(arg.size) { atan(array[it]) })
} else {
DoubleBuffer(DoubleArray(arg.size) { atan(arg[it]) })
}
override fun power(arg: Buffer<Double>, pow: Number): DoubleBuffer = if (arg is DoubleBuffer) {
val array = arg.array
DoubleBuffer(DoubleArray(arg.size) { array[it].pow(pow.toDouble()) })
} else {
DoubleBuffer(DoubleArray(arg.size) { arg[it].pow(pow.toDouble()) })
}
override fun exp(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
val array = arg.array
DoubleBuffer(DoubleArray(arg.size) { exp(array[it]) })
} else {
DoubleBuffer(DoubleArray(arg.size) { exp(arg[it]) })
}
override fun ln(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
val array = arg.array
DoubleBuffer(DoubleArray(arg.size) { ln(array[it]) })
} else {
DoubleBuffer(DoubleArray(arg.size) { ln(arg[it]) })
} }
} }
@ -119,7 +142,6 @@ class RealBufferField(val size: Int) : ExtendedField<Buffer<Double>> {
return RealBufferFieldOperations.multiply(a, b) return RealBufferFieldOperations.multiply(a, b)
} }
override fun divide(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer { override fun divide(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
require(a.size == size) { "The buffer size ${a.size} does not match context size $size" } require(a.size == size) { "The buffer size ${a.size} does not match context size $size" }
return RealBufferFieldOperations.divide(a, b) return RealBufferFieldOperations.divide(a, b)
@ -135,6 +157,26 @@ class RealBufferField(val size: Int) : ExtendedField<Buffer<Double>> {
return RealBufferFieldOperations.cos(arg) return RealBufferFieldOperations.cos(arg)
} }
override fun tan(arg: Buffer<Double>): DoubleBuffer {
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
return RealBufferFieldOperations.tan(arg)
}
override fun asin(arg: Buffer<Double>): DoubleBuffer {
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
return RealBufferFieldOperations.asin(arg)
}
override fun acos(arg: Buffer<Double>): DoubleBuffer {
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
return RealBufferFieldOperations.acos(arg)
}
override fun atan(arg: Buffer<Double>): DoubleBuffer {
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
return RealBufferFieldOperations.atan(arg)
}
override fun power(arg: Buffer<Double>, pow: Number): DoubleBuffer { override fun power(arg: Buffer<Double>, pow: Number): DoubleBuffer {
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
return RealBufferFieldOperations.power(arg, pow) return RealBufferFieldOperations.power(arg, pow)
@ -149,5 +191,4 @@ class RealBufferField(val size: Int) : ExtendedField<Buffer<Double>> {
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
return RealBufferFieldOperations.ln(arg) return RealBufferFieldOperations.ln(arg)
} }
} }

View File

@ -74,6 +74,13 @@ class RealNDField(override val shape: IntArray) :
override fun cos(arg: NDBuffer<Double>) = map(arg) { cos(it) } override fun cos(arg: NDBuffer<Double>) = map(arg) { cos(it) }
override fun tan(arg: NDBuffer<Double>): NDBuffer<Double> = map(arg) { tan(it) }
override fun asin(arg: NDBuffer<Double>): NDBuffer<Double> = map(arg) { asin(it) }
override fun acos(arg: NDBuffer<Double>): NDBuffer<Double> = map(arg) { acos(it) }
override fun atan(arg: NDBuffer<Double>): NDBuffer<Double> = map(arg) { atan(it) }
} }

View File

@ -11,18 +11,16 @@ class DataViewMemory(val view: DataView) : Memory {
override fun view(offset: Int, length: Int): Memory { override fun view(offset: Int, length: Int): Memory {
require(offset >= 0) { "offset shouldn't be negative: $offset" } require(offset >= 0) { "offset shouldn't be negative: $offset" }
require(length >= 0) { "length shouldn't be negative: $length" } require(length >= 0) { "length shouldn't be negative: $length" }
if (offset + length > size) {
if (offset + length > size)
throw IndexOutOfBoundsException("offset + length > size: $offset + $length > $size") throw IndexOutOfBoundsException("offset + length > size: $offset + $length > $size")
}
return DataViewMemory(DataView(view.buffer, view.byteOffset + offset, length)) return DataViewMemory(DataView(view.buffer, view.byteOffset + offset, length))
} }
override fun copy(): Memory = DataViewMemory(DataView(view.buffer.slice(0)))
override fun copy(): Memory { private val reader: MemoryReader = object : MemoryReader {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
private val reader = object : MemoryReader {
override val memory: Memory get() = this@DataViewMemory override val memory: Memory get() = this@DataViewMemory
override fun readDouble(offset: Int): Double = view.getFloat64(offset, false) override fun readDouble(offset: Int): Double = view.getFloat64(offset, false)
@ -45,7 +43,7 @@ class DataViewMemory(val view: DataView) : Memory {
override fun reader(): MemoryReader = reader override fun reader(): MemoryReader = reader
private val writer = object : MemoryWriter { private val writer: MemoryWriter = object : MemoryWriter {
override val memory: Memory get() = this@DataViewMemory override val memory: Memory get() = this@DataViewMemory
override fun writeDouble(offset: Int, value: Double) { override fun writeDouble(offset: Int, value: Double) {
@ -76,7 +74,6 @@ class DataViewMemory(val view: DataView) : Memory {
override fun release() { override fun release() {
//does nothing on JS //does nothing on JS
} }
} }
override fun writer(): MemoryWriter = writer override fun writer(): MemoryWriter = writer