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 sin(arg: DerivativeStructure): DerivativeStructure = arg.sin()
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) {
is Double -> arg.pow(pow)
@ -136,6 +138,3 @@ object DiffExpressionAlgebra : ExpressionAlgebra<Double, DiffExpression>, Field<
override fun divide(a: DiffExpression, b: DiffExpression) =
DiffExpression { a.function(this) / b.function(this) }
}

View File

@ -8,6 +8,8 @@ import scientifik.memory.MemorySpec
import scientifik.memory.MemoryWriter
import kotlin.math.*
private val PI_DIV_2 = Complex(PI / 2, 0)
/**
* 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)
}
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 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 =
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> :
FieldOperations<T>,
TrigonometricOperations<T>,
InverseTrigonometricOperations<T>,
PowerOperations<T>,
ExponentialOperations<T> {
@ -20,19 +20,16 @@ interface ExtendedFieldOperations<T> :
PowerOperations.SQRT_OPERATION -> sqrt(arg)
ExponentialOperations.EXP_OPERATION -> exp(arg)
ExponentialOperations.LN_OPERATION -> ln(arg)
else -> super.unaryOperation(operation, arg)
else -> super<InverseTrigonometricOperations>.unaryOperation(operation, arg)
}
}
interface ExtendedField<T> : ExtendedFieldOperations<T>, Field<T> {
override fun rightSideNumberOperation(operation: String, left: T, right: Number): T {
return when (operation) {
override fun rightSideNumberOperation(operation: String, left: T, right: Number): T = when (operation) {
PowerOperations.POW_OPERATION -> power(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 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())
@ -97,8 +97,10 @@ object FloatField : ExtendedField<Float>, Norm<Float, Float> {
override inline fun sin(arg: Float) = kotlin.math.sin(arg)
override inline fun cos(arg: Float) = kotlin.math.cos(arg)
override inline fun tan(arg: Float): Float = kotlin.math.tan(arg)
override inline fun tan(arg: 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())

View File

@ -10,28 +10,43 @@ package scientifik.kmath.operations
* It also allows to override behavior for optional operations
*
*/
interface TrigonometricOperations<T> {
interface TrigonometricOperations<T> : FieldOperations<T> {
fun sin(arg: T): T
fun cos(arg: T): T
fun tan(arg: T): T
fun tan(arg: T): T = sin(arg) / cos(arg)
companion object {
const val SIN_OPERATION = "sin"
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>>> 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 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 */
/**
* 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 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 */
interface ExponentialOperations<T> {
interface ExponentialOperations<T>: Algebra<T> {
fun exp(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 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]
*/
object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
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} " }
return if (a is DoubleBuffer && b is DoubleBuffer) {
val aArray = a.array
val bArray = b.array
@ -22,6 +24,7 @@ object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
override fun multiply(a: Buffer<Double>, k: Number): DoubleBuffer {
val kValue = k.toDouble()
return if (a is DoubleBuffer) {
val aArray = a.array
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 {
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) {
val aArray = a.array
val bArray = b.array
@ -43,6 +47,7 @@ object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
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} " }
return if (a is DoubleBuffer && b is DoubleBuffer) {
val aArray = a.array
val bArray = b.array
@ -52,50 +57,68 @@ object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
}
}
override fun sin(arg: Buffer<Double>): DoubleBuffer {
return if (arg is DoubleBuffer) {
override fun sin(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
val array = arg.array
DoubleBuffer(DoubleArray(arg.size) { sin(array[it]) })
} else {
DoubleBuffer(DoubleArray(arg.size) { sin(arg[it]) })
}
}
override fun cos(arg: Buffer<Double>): DoubleBuffer {
return if (arg is DoubleBuffer) {
override fun cos(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
val array = arg.array
DoubleBuffer(DoubleArray(arg.size) { cos(array[it]) })
} else {
DoubleBuffer(DoubleArray(arg.size) { cos(arg[it]) })
}
override fun tan(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
val array = arg.array
DoubleBuffer(DoubleArray(arg.size) { tan(array[it]) })
} else {
DoubleBuffer(DoubleArray(arg.size) { tan(arg[it]) })
}
override fun power(arg: Buffer<Double>, pow: Number): DoubleBuffer {
return if (arg is DoubleBuffer) {
override fun asin(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
val array = arg.array
DoubleBuffer(DoubleArray(arg.size) { asin(array[it]) })
} else {
DoubleBuffer(DoubleArray(arg.size) { asin(arg[it]) })
}
override fun acos(arg: Buffer<Double>): DoubleBuffer = if (arg is DoubleBuffer) {
val array = arg.array
DoubleBuffer(DoubleArray(arg.size) { acos(array[it]) })
} else {
DoubleBuffer(DoubleArray(arg.size) { acos(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 {
return if (arg is DoubleBuffer) {
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 {
return if (arg is DoubleBuffer) {
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]) })
}
}
}
class RealBufferField(val size: Int) : ExtendedField<Buffer<Double>> {
@ -119,7 +142,6 @@ class RealBufferField(val size: Int) : ExtendedField<Buffer<Double>> {
return RealBufferFieldOperations.multiply(a, b)
}
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" }
return RealBufferFieldOperations.divide(a, b)
@ -135,6 +157,26 @@ class RealBufferField(val size: Int) : ExtendedField<Buffer<Double>> {
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 {
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
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" }
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 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 {
require(offset >= 0) { "offset shouldn't be negative: $offset" }
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")
}
return DataViewMemory(DataView(view.buffer, view.byteOffset + offset, length))
}
override fun copy(): Memory = DataViewMemory(DataView(view.buffer.slice(0)))
override fun copy(): Memory {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
private val reader = object : MemoryReader {
private val reader: MemoryReader = object : MemoryReader {
override val memory: Memory get() = this@DataViewMemory
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
private val writer = object : MemoryWriter {
private val writer: MemoryWriter = object : MemoryWriter {
override val memory: Memory get() = this@DataViewMemory
override fun writeDouble(offset: Int, value: Double) {
@ -76,7 +74,6 @@ class DataViewMemory(val view: DataView) : Memory {
override fun release() {
//does nothing on JS
}
}
override fun writer(): MemoryWriter = writer