diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 467a867bc..adc74adfe 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,101 +1,17 @@ name: Gradle build -on: [ push ] +on: [push] jobs: - build-ubuntu: - runs-on: ubuntu-20.04 + build: + + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Install Chrome - run: | - sudo apt install -y libappindicator1 fonts-liberation - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome*.deb - - name: Cache gradle - uses: actions/cache@v2 - with: - path: | - .gradle - build - ~/.gradle - key: gradle - restore-keys: gradle - - - name: Cache konan - uses: actions/cache@v2 - with: - path: | - ~/.konan/dependencies - ~/.konan/kotlin-native-prebuilt-linux-* - key: ${{ runner.os }}-konan - restore-keys: ${{ runner.os }}-konan - - name: Build with Gradle - run: ./gradlew -Dorg.gradle.daemon=false --build-cache build - - build-osx: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Cache gradle - uses: actions/cache@v2 - with: - path: | - .gradle - build - ~/.gradle - key: gradle - restore-keys: gradle - - - name: Cache konan - uses: actions/cache@v2 - with: - path: | - ~/.konan/dependencies - ~/.konan/kotlin-native-prebuilt-macos-* - key: ${{ runner.os }}-konan - restore-keys: ${{ runner.os }}-konan - - name: Build with Gradle - run: sudo ./gradlew -Dorg.gradle.daemon=false --build-cache build - - build-windows: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Add msys to path - run: SETX PATH "%PATH%;C:\msys64\mingw64\bin" - - name: Cache gradle - uses: actions/cache@v2 - with: - path: | - .gradle - build - ~/.gradle - key: ${{ runner.os }}-gradle - restore-keys: ${{ runner.os }}-gradle - - - name: Cache konan - uses: actions/cache@v2 - with: - path: | - ~/.konan/dependencies - ~/.konan/kotlin-native-prebuilt-mingw-* - key: ${{ runner.os }}-konan - restore-keys: ${{ runner.os }}-konan - - name: Build with Gradle - run: ./gradlew --build-cache build + - uses: actions/checkout@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Build with Gradle + run: ./gradlew build diff --git a/.gitignore b/.gitignore index bade7f08c..a9294eff9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ out/ # Cache of project .gradletasknamecache + +gradle.properties \ No newline at end of file diff --git a/.space.kts b/.space.kts deleted file mode 100644 index d70ad6d59..000000000 --- a/.space.kts +++ /dev/null @@ -1,3 +0,0 @@ -job("Build") { - gradlew("openjdk:11", "build") -} diff --git a/CHANGELOG.md b/CHANGELOG.md index e542d210c..2c99c7bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,52 +1,6 @@ # KMath ## [Unreleased] -### Added -- `fun` annotation for SAM interfaces in library -- Explicit `public` visibility for all public APIs -- Better trigonometric and hyperbolic functions for `AutoDiffField` (https://github.com/mipt-npm/kmath/pull/140). -- Automatic README generation for features (#139) -- Native support for `memory`, `core` and `dimensions` -- `kmath-ejml` to supply EJML SimpleMatrix wrapper (https://github.com/mipt-npm/kmath/pull/136). -- A separate `Symbol` entity, which is used for global unbound symbol. -- A `Symbol` indexing scope. -- Basic optimization API for Commons-math. -- Chi squared optimization for array-like data in CM -- `Fitting` utility object in prob/stat -- ND4J support module submitting `NDStructure` and `NDAlgebra` over `INDArray`. -- Coroutine-deterministic Monte-Carlo scope with a random number generator. -- Some minor utilities to `kmath-for-real`. -- Generic operation result parameter to `MatrixContext` - -### Changed -- Package changed from `scientifik` to `kscience.kmath`. -- Gradle version: 6.6 -> 6.7.1 -- Minor exceptions refactor (throwing `IllegalArgumentException` by argument checks instead of `IllegalStateException`) -- `Polynomial` secondary constructor made function. -- Kotlin version: 1.3.72 -> 1.4.20 -- `kmath-ast` doesn't depend on heavy `kotlin-reflect` library. -- Full autodiff refactoring based on `Symbol` -- `kmath-prob` renamed to `kmath-stat` -- Grid generators moved to `kmath-for-real` -- Use `Point` instead of specialized type in `kmath-for-real` -- Optimized dot product for buffer matrices moved to `kmath-for-real` -- EjmlMatrix context is an object -- Matrix LUP `inverse` renamed to `inverseWithLUP` - -### Deprecated - -### Removed -- `kmath-koma` module because it doesn't support Kotlin 1.4. -- Support of `legacy` JS backend (we will support only IR) -- `toGrid` method. -- Public visibility of `BufferAccessor2D` - -### Fixed -- `symbol` method in `MstExtendedField` (https://github.com/mipt-npm/kmath/pull/140) - -### Security - -## [0.1.4] ### Added - Functional Expressions API @@ -62,23 +16,17 @@ - Local coding conventions - Geometric Domains API in `kmath-core` - Blocking chains in `kmath-coroutines` -- Full hyperbolic functions support and default implementations within `ExtendedField` -- Norm support for `Complex` ### Changed -- `readAsMemory` now has `throws IOException` in JVM signature. -- Several functions taking functional types were made `inline`. -- Several functions taking functional types now have `callsInPlace` contracts. - BigInteger and BigDecimal algebra: JBigDecimalField has companion object with default math context; minor optimizations - `power(T, Int)` extension function has preconditions and supports `Field` - Memory objects have more preconditions (overflow checking) - `tg` function is renamed to `tan` (https://github.com/mipt-npm/kmath/pull/114) -- Gradle version: 6.3 -> 6.6 -- Moved probability distributions to commons-rng and to `kmath-prob` +- Gradle version: 6.3 -> 6.5.1 +- Moved probability distributions to commons-rng and to `kmath-prob`. ### Fixed - Missing copy method in Memory implementation on JS (https://github.com/mipt-npm/kmath/pull/106) - D3.dim value in `kmath-dimensions` - Multiplication in integer rings in `kmath-core` (https://github.com/mipt-npm/kmath/pull/101) - Commons RNG compatibility (https://github.com/mipt-npm/kmath/issues/93) -- Multiplication of BigInt by scalar diff --git a/README.md b/README.md index 50a916d2c..a081ceedd 100644 --- a/README.md +++ b/README.md @@ -3,55 +3,41 @@ ![Gradle build](https://github.com/mipt-npm/kmath/workflows/Gradle%20build/badge.svg) -Bintray: [ ![Download](https://api.bintray.com/packages/mipt-npm/kscience/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/kscience/kmath-core/_latestVersion) +Bintray: [ ![Download](https://api.bintray.com/packages/mipt-npm/scientifik/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) Bintray-dev: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/dev/kmath-core/_latestVersion) # KMath - -Could be pronounced as `key-math`. The Kotlin MATHematics library was initially intended as a Kotlin-based analog to -Python's NumPy library. Later we found that kotlin is much more flexible language and allows superior architecture -designs. In contrast to `numpy` and `scipy` it is modular and has a lightweight core. The `numpy`-like experience could -be achieved with [kmath-for-real](/kmath-for-real) extension module. - -## Publications and talks - -* [A conceptual article about context-oriented design](https://proandroiddev.com/an-introduction-context-oriented-programming-in-kotlin-2e79d316b0a2) -* [Another article about context-oriented design](https://proandroiddev.com/diving-deeper-into-context-oriented-programming-in-kotlin-3ecb4ec38814) -* [ACAT 2019 conference paper](https://aip.scitation.org/doi/abs/10.1063/1.5130103) +Could be pronounced as `key-math`. +The Kotlin MATHematics library is intended as a Kotlin-based analog to Python's `numpy` library. In contrast to `numpy` and `scipy` it is modular and has a lightweight core. # Goal - -* Provide a flexible and powerful API to work with mathematics abstractions in Kotlin-multiplatform (JVM, JS and Native). +* Provide a flexible and powerful API to work with mathematics abstractions in Kotlin-multiplatform (JVM and JS for now and Native in future). * Provide basic multiplatform implementations for those abstractions (without significant performance optimization). * Provide bindings and wrappers with those abstractions for popular optimized platform libraries. ## Non-goals - -* Be like NumPy. It was the idea at the beginning, but we decided that we can do better in terms of API. -* Provide the best performance out of the box. We have specialized libraries for that. Need only API wrappers for them. +* Be like Numpy. It was the idea at the beginning, but we decided that we can do better in terms of API. +* Provide best performance out of the box. We have specialized libraries for that. Need only API wrappers for them. * Cover all cases as immediately and in one bundle. We will modularize everything and add new features gradually. -* Provide specialized behavior in the core. API is made generic on purpose, so one needs to specialize for types, like -for `Double` in the core. For that we will have specialization modules like `for-real`, which will give better -experience for those, who want to work with specific types. +* Provide specialized behavior in the core. API is made generic on purpose, so one needs to specialize for types, like for `Double` in the core. For that we will have specialization modules like `for-real`, which will give better experience for those, who want to work with specific types. ## Features -Current feature list is [here](/docs/features.md) +Actual feature list is [here](doc/features.md) * **Algebra** - * Algebraic structures like rings, spaces and fields (**TODO** add example to wiki) + * Algebraic structures like rings, spaces and field (**TODO** add example to wiki) * Basic linear algebra operations (sums, products, etc.), backed by the `Space` API. - * Complex numbers backed by the `Field` API (meaning they will be usable in any structure like vectors and - N-dimensional arrays). + * Complex numbers backed by the `Field` API (meaning that they will be usable in any structure like vectors and N-dimensional arrays). * Advanced linear algebra operations like matrix inversion and LU decomposition. * **Array-like structures** Full support of many-dimensional array-like structures including mixed arithmetic operations and function operations over arrays and numbers (with the added benefit of static type checking). -* **Expressions** By writing a single mathematical expression once, users will be able to apply different types of -objects to the expression by providing a context. Expressions can be used for a wide variety of purposes from high -performance calculations to code generation. +* **Expressions** By writing a single mathematical expression +once, users will be able to apply different types of objects to the expression by providing a context. Expressions +can be used for a wide variety of purposes from high performance calculations to code generation. * **Histograms** Fast multi-dimensional histograms. @@ -59,11 +45,13 @@ performance calculations to code generation. * **Type-safe dimensions** Type-safe dimensions for matrix operations. -* **Commons-math wrapper** It is planned to gradually wrap most parts of -[Apache commons-math](http://commons.apache.org/proper/commons-math/) library in Kotlin code and maybe rewrite some -parts to better suit the Kotlin programming paradigm, however there is no established roadmap for that. Feel free to -submit a feature request if you want something to be implemented first. +* **Commons-math wrapper** It is planned to gradually wrap most parts of [Apache commons-math](http://commons.apache.org/proper/commons-math/) + library in Kotlin code and maybe rewrite some parts to better suit the Kotlin programming paradigm, however there is no fixed roadmap for that. Feel free + to submit a feature request if you want something to be done first. +* **Koma wrapper** [Koma](https://github.com/kyonifer/koma) is a well established numerics library in Kotlin, specifically linear algebra. +The plan is to have wrappers for koma implementations for compatibility with kmath API. + ## Planned features * **Messaging** A mathematical notation to support multi-language and multi-node communication for mathematical tasks. @@ -76,179 +64,41 @@ submit a feature request if you want something to be implemented first. * **Fitting** Non-linear curve fitting facilities -## Modules - -
- -* ### [examples](examples) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-ast](kmath-ast) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-commons](kmath-commons) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-core](kmath-core) -> Core classes, algebra definitions, basic linear algebra -> -> **Maturity**: DEVELOPMENT -> -> **Features:** -> - [algebras](kmath-core/src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt) : Algebraic structures: contexts and elements -> - [nd](kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt) : Many-dimensional structures -> - [buffers](kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt) : One-dimensional structure -> - [expressions](kmath-core/src/commonMain/kotlin/kscience/kmath/expressions) : Functional Expressions -> - [domains](kmath-core/src/commonMain/kotlin/kscience/kmath/domains) : Domains -> - [autodif](kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SimpleAutoDiff.kt) : Automatic differentiation - -
- -* ### [kmath-coroutines](kmath-coroutines) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-dimensions](kmath-dimensions) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-ejml](kmath-ejml) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-for-real](kmath-for-real) -> Extension module that should be used to achieve numpy-like behavior. -All operations are specialized to work with `Double` numbers without declaring algebraic contexts. -One can still use generic algebras though. -> -> **Maturity**: EXPERIMENTAL -> -> **Features:** -> - [RealVector](kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealVector.kt) : Numpy-like operations for Buffers/Points -> - [RealMatrix](kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt) : Numpy-like operations for 2d real structures -> - [grids](kmath-for-real/src/commonMain/kotlin/kscience/kmath/structures/grids.kt) : Uniform grid generators - -
- -* ### [kmath-functions](kmath-functions) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-geometry](kmath-geometry) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-histograms](kmath-histograms) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-kotlingrad](kmath-kotlingrad) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-memory](kmath-memory) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-nd4j](kmath-nd4j) -> ND4J NDStructure implementation and according NDAlgebra classes -> -> **Maturity**: EXPERIMENTAL -> -> **Features:** -> - [nd4jarraystructure](kmath-nd4j/src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt) : NDStructure wrapper for INDArray -> - [nd4jarrayrings](kmath-nd4j/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt) : Rings over Nd4jArrayStructure of Int and Long -> - [nd4jarrayfields](kmath-nd4j/src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt) : Fields over Nd4jArrayStructure of Float and Double - -
- -* ### [kmath-stat](kmath-stat) -> -> -> **Maturity**: EXPERIMENTAL -
- -* ### [kmath-viktor](kmath-viktor) -> -> -> **Maturity**: EXPERIMENTAL -
- - ## Multi-platform support -KMath is developed as a multi-platform library, which means that most of the interfaces are declared in the -[common source sets](/kmath-core/src/commonMain) and implemented there wherever it is possible. In some cases, features -are delegated to platform-specific implementations even if they could be provided in the common module for performance -reasons. Currently, the Kotlin/JVM is the primary platform, however Kotlin/Native and Kotlin/JS contributions and -feedback are also welcome. +KMath is developed as a multi-platform library, which means that most of interfaces are declared in the [common module](kmath-core/src/commonMain). Implementation is also done in the common module wherever possible. In some cases, features are delegated to platform-specific implementations even if they could be done in the common module for performance reasons. Currently, the JVM is the main focus of development, however Kotlin/Native and Kotlin/JS contributions are also welcome. ## Performance -Calculation performance is one of major goals of KMath in the future, but in some cases it is impossible to achieve -both performance and flexibility. +Calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve both performance and flexibility. We expect to focus on creating convenient universal API first and then work on increasing performance for specific cases. We expect the worst KMath benchmarks will perform better than native Python, but worse than optimized native/SciPy (mostly due to boxing operations on primitive numbers). The best performance of optimized parts could be better than SciPy. -We expect to focus on creating convenient universal API first and then work on increasing performance for specific -cases. We expect the worst KMath benchmarks will perform better than native Python, but worse than optimized -native/SciPy (mostly due to boxing operations on primitive numbers). The best performance of optimized parts could be -better than SciPy. +### Dependency -### Repositories - -Release artifacts are accessible from bintray with following configuration (see documentation of -[Kotlin Multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html) for more details): +Release artifacts are accessible from bintray with following configuration (see documentation for [kotlin-multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html) form more details): ```kotlin -repositories { - maven("https://dl.bintray.com/mipt-npm/kscience") +repositories{ + maven("https://dl.bintray.com/mipt-npm/scientifik") } -dependencies { - api("kscience.kmath:kmath-core:0.2.0-dev-4") - // api("kscience.kmath:kmath-core-jvm:0.2.0-dev-4") for jvm-specific version +dependencies{ + api("scientifik:kmath-core:${kmathVersion}") + //api("scientifik:kmath-core-jvm:${kmathVersion}") for jvm-specific version } ``` Gradle `6.0+` is required for multiplatform artifacts. -#### Development - -Development builds are uploaded to the separate repository: +### Development +Development builds are accessible from the reposirtory ```kotlin -repositories { +repositories{ maven("https://dl.bintray.com/mipt-npm/dev") } ``` +with the same artifact names. ## Contributing -The project requires a lot of additional work. The most important thing we need is a feedback about what features are -required the most. Feel free to create feature requests. We are also welcome to code contributions, -especially in issues marked with -[waiting for a hero](https://github.com/mipt-npm/kmath/labels/waiting%20for%20a%20hero) label. +The project requires a lot of additional work. Please feel free to contribute in any way and propose new features. diff --git a/build.gradle.kts b/build.gradle.kts index 561a2212b..8a2ba3617 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,40 +1,25 @@ -import ru.mipt.npm.gradle.KSciencePublishPlugin - plugins { - id("ru.mipt.npm.project") + id("scientifik.publish") apply false } -internal val kmathVersion: String by extra("0.2.0-dev-4") -internal val bintrayRepo: String by extra("kscience") -internal val githubProject: String by extra("kmath") +val kmathVersion by extra("0.1.4-dev-8") + +val bintrayRepo by extra("scientifik") +val githubProject by extra("kmath") allprojects { repositories { jcenter() - maven("https://clojars.org/repo") - maven("https://dl.bintray.com/egor-bogomolov/astminer/") - maven("https://dl.bintray.com/hotkeytlt/maven") - maven("https://dl.bintray.com/kotlin/kotlin-eap") maven("https://dl.bintray.com/kotlin/kotlinx") - maven("https://dl.bintray.com/mipt-npm/dev") - maven("https://dl.bintray.com/mipt-npm/kscience") - maven("https://jitpack.io") - maven("http://logicrunch.research.it.uu.se/maven/") - mavenCentral() + maven("https://dl.bintray.com/hotkeytlt/maven") } - group = "kscience.kmath" + group = "scientifik" version = kmathVersion } subprojects { - if (name.startsWith("kmath")) apply() -} - -readme { - readmeTemplate = file("docs/templates/README-TEMPLATE.md") -} - -apiValidation { - validationDisabled = true -} + if (name.startsWith("kmath")) { + apply(plugin = "scientifik.publish") + } +} \ No newline at end of file diff --git a/docs/algebra.md b/doc/algebra.md similarity index 97% rename from docs/algebra.md rename to doc/algebra.md index c3227517f..b1b77a31f 100644 --- a/docs/algebra.md +++ b/doc/algebra.md @@ -5,7 +5,7 @@ operation, say `+`, one needs two objects of a type `T` and an algebra context, say `Space`. Next one needs to run the actual operation in the context: ```kotlin -import kscience.kmath.operations.* +import scientifik.kmath.operations.* val a: T = ... val b: T = ... @@ -47,7 +47,7 @@ but it also holds reference to the `ComplexField` singleton, which allows perfor numbers without explicit involving the context like: ```kotlin -import kscience.kmath.operations.* +import scientifik.kmath.operations.* // Using elements val c1 = Complex(1.0, 1.0) @@ -82,7 +82,7 @@ operations in all performance-critical places. The performance of element operat KMath submits both contexts and elements for builtin algebraic structures: ```kotlin -import kscience.kmath.operations.* +import scientifik.kmath.operations.* val c1 = Complex(1.0, 2.0) val c2 = ComplexField.i @@ -95,7 +95,7 @@ val c3 = ComplexField { c1 + c2 } Also, `ComplexField` features special operations to mix complex and real numbers, for example: ```kotlin -import kscience.kmath.operations.* +import scientifik.kmath.operations.* val c1 = Complex(1.0, 2.0) val c2 = ComplexField { c1 - 1.0 } // Returns: Complex(re=0.0, im=2.0) diff --git a/docs/buffers.md b/doc/buffers.md similarity index 100% rename from docs/buffers.md rename to doc/buffers.md diff --git a/docs/codestyle.md b/doc/codestyle.md similarity index 100% rename from docs/codestyle.md rename to doc/codestyle.md diff --git a/docs/contexts.md b/doc/contexts.md similarity index 100% rename from docs/contexts.md rename to doc/contexts.md diff --git a/docs/expressions.md b/doc/expressions.md similarity index 100% rename from docs/expressions.md rename to doc/expressions.md diff --git a/doc/features.md b/doc/features.md new file mode 100644 index 000000000..e6a820c1e --- /dev/null +++ b/doc/features.md @@ -0,0 +1,17 @@ +# Features + +* [Algebra](./algebra.md) - [Context-based](./contexts.md) operations on different primitives and structures. + +* [NDStructures](./nd-structure.md) + +* [Linear algebra](./linear.md) - Matrices, operations and linear equations solving. To be moved to separate module. Currently supports basic +api and multiple library back-ends. + +* [Histograms](./histograms.md) - Multidimensional histogram calculation and operations. + +* [Expressions](./expressions.md) + +* Commons math integration + +* Koma integration + diff --git a/docs/histograms.md b/doc/histograms.md similarity index 100% rename from docs/histograms.md rename to doc/histograms.md diff --git a/docs/linear.md b/doc/linear.md similarity index 79% rename from docs/linear.md rename to doc/linear.md index 6ccc6caac..883df275e 100644 --- a/docs/linear.md +++ b/doc/linear.md @@ -6,10 +6,10 @@ back-ends. The new operations added as extensions to contexts instead of being m Two major contexts used for linear algebra and hyper-geometry: -* `VectorSpace` forms a mathematical space on top of array-like structure (`Buffer` and its type alias `Point` used for geometry). +* `VectorSpace` forms a mathematical space on top of array-like structure (`Buffer` and its typealias `Point` used for geometry). * `MatrixContext` forms a space-like context for 2d-structures. It does not store matrix size and therefore does not implement -`Space` interface (it is impossible to create zero element without knowing the matrix size). +`Space` interface (it is not possible to create zero element without knowing the matrix size). ## Vector spaces diff --git a/docs/nd-structure.md b/doc/nd-structure.md similarity index 100% rename from docs/nd-structure.md rename to doc/nd-structure.md diff --git a/docs/features.md b/docs/features.md deleted file mode 100644 index 1068a4417..000000000 --- a/docs/features.md +++ /dev/null @@ -1,14 +0,0 @@ -# Features - -* [Algebra](algebra.md) - [Context-based](contexts.md) operations on different primitives and structures. - -* [NDStructures](nd-structure.md) - -* [Linear algebra](linear.md) - Matrices, operations and linear equations solving. To be moved to separate module. Currently supports basic -api and multiple library back-ends. - -* [Histograms](histograms.md) - Multidimensional histogram calculation and operations. - -* [Expressions](expressions.md) - -* Commons math integration diff --git a/docs/images/KM.svg b/docs/images/KM.svg deleted file mode 100644 index 50126cbc5..000000000 --- a/docs/images/KM.svg +++ /dev/null @@ -1,59 +0,0 @@ - -image/svg+xml \ No newline at end of file diff --git a/docs/images/KM_mono.svg b/docs/images/KM_mono.svg deleted file mode 100644 index 3b6890b6b..000000000 --- a/docs/images/KM_mono.svg +++ /dev/null @@ -1,55 +0,0 @@ - -image/svg+xml \ No newline at end of file diff --git a/docs/images/KMath.svg b/docs/images/KMath.svg deleted file mode 100644 index d88cfe7b0..000000000 --- a/docs/images/KMath.svg +++ /dev/null @@ -1,91 +0,0 @@ - -image/svg+xml \ No newline at end of file diff --git a/docs/images/KMath_mono.svg b/docs/images/KMath_mono.svg deleted file mode 100644 index 3a62ac383..000000000 --- a/docs/images/KMath_mono.svg +++ /dev/null @@ -1,371 +0,0 @@ - -image/svg+xml \ No newline at end of file diff --git a/docs/templates/ARTIFACT-TEMPLATE.md b/docs/templates/ARTIFACT-TEMPLATE.md deleted file mode 100644 index c77948d4b..000000000 --- a/docs/templates/ARTIFACT-TEMPLATE.md +++ /dev/null @@ -1,37 +0,0 @@ -> #### Artifact: -> -> This module artifact: `${group}:${name}:${version}`. -> -> Bintray release version: [ ![Download](https://api.bintray.com/packages/mipt-npm/kscience/${name}/images/download.svg) ](https://bintray.com/mipt-npm/kscience/${name}/_latestVersion) -> -> Bintray development version: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/${name}/images/download.svg) ](https://bintray.com/mipt-npm/dev/${name}/_latestVersion) -> -> **Gradle:** -> -> ```gradle -> repositories { -> maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } -> maven { url 'https://dl.bintray.com/mipt-npm/kscience' } -> maven { url 'https://dl.bintray.com/mipt-npm/dev' } -> maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - -> } -> -> dependencies { -> implementation '${group}:${name}:${version}' -> } -> ``` -> **Gradle Kotlin DSL:** -> -> ```kotlin -> repositories { -> maven("https://dl.bintray.com/kotlin/kotlin-eap") -> maven("https://dl.bintray.com/mipt-npm/kscience") -> maven("https://dl.bintray.com/mipt-npm/dev") -> maven("https://dl.bintray.com/hotkeytlt/maven") -> } -> -> dependencies { -> implementation("${group}:${name}:${version}") -> } -> ``` \ No newline at end of file diff --git a/docs/templates/README-TEMPLATE.md b/docs/templates/README-TEMPLATE.md deleted file mode 100644 index ee1df818c..000000000 --- a/docs/templates/README-TEMPLATE.md +++ /dev/null @@ -1,134 +0,0 @@ -[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) -[![DOI](https://zenodo.org/badge/129486382.svg)](https://zenodo.org/badge/latestdoi/129486382) - -![Gradle build](https://github.com/mipt-npm/kmath/workflows/Gradle%20build/badge.svg) - -Bintray: [ ![Download](https://api.bintray.com/packages/mipt-npm/kscience/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/kscience/kmath-core/_latestVersion) - -Bintray-dev: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/dev/kmath-core/_latestVersion) - -# KMath - -Could be pronounced as `key-math`. The Kotlin MATHematics library was initially intended as a Kotlin-based analog to -Python's NumPy library. Later we found that kotlin is much more flexible language and allows superior architecture -designs. In contrast to `numpy` and `scipy` it is modular and has a lightweight core. The `numpy`-like experience could -be achieved with [kmath-for-real](/kmath-for-real) extension module. - -## Publications and talks - -* [A conceptual article about context-oriented design](https://proandroiddev.com/an-introduction-context-oriented-programming-in-kotlin-2e79d316b0a2) -* [Another article about context-oriented design](https://proandroiddev.com/diving-deeper-into-context-oriented-programming-in-kotlin-3ecb4ec38814) -* [ACAT 2019 conference paper](https://aip.scitation.org/doi/abs/10.1063/1.5130103) - -# Goal - -* Provide a flexible and powerful API to work with mathematics abstractions in Kotlin-multiplatform (JVM, JS and Native). -* Provide basic multiplatform implementations for those abstractions (without significant performance optimization). -* Provide bindings and wrappers with those abstractions for popular optimized platform libraries. - -## Non-goals - -* Be like NumPy. It was the idea at the beginning, but we decided that we can do better in terms of API. -* Provide the best performance out of the box. We have specialized libraries for that. Need only API wrappers for them. -* Cover all cases as immediately and in one bundle. We will modularize everything and add new features gradually. -* Provide specialized behavior in the core. API is made generic on purpose, so one needs to specialize for types, like -for `Double` in the core. For that we will have specialization modules like `for-real`, which will give better -experience for those, who want to work with specific types. - -## Features - -Current feature list is [here](/docs/features.md) - -* **Algebra** - * Algebraic structures like rings, spaces and fields (**TODO** add example to wiki) - * Basic linear algebra operations (sums, products, etc.), backed by the `Space` API. - * Complex numbers backed by the `Field` API (meaning they will be usable in any structure like vectors and - N-dimensional arrays). - * Advanced linear algebra operations like matrix inversion and LU decomposition. - -* **Array-like structures** Full support of many-dimensional array-like structures -including mixed arithmetic operations and function operations over arrays and numbers (with the added benefit of static type checking). - -* **Expressions** By writing a single mathematical expression once, users will be able to apply different types of -objects to the expression by providing a context. Expressions can be used for a wide variety of purposes from high -performance calculations to code generation. - -* **Histograms** Fast multi-dimensional histograms. - -* **Streaming** Streaming operations on mathematical objects and objects buffers. - -* **Type-safe dimensions** Type-safe dimensions for matrix operations. - -* **Commons-math wrapper** It is planned to gradually wrap most parts of -[Apache commons-math](http://commons.apache.org/proper/commons-math/) library in Kotlin code and maybe rewrite some -parts to better suit the Kotlin programming paradigm, however there is no established roadmap for that. Feel free to -submit a feature request if you want something to be implemented first. - -## Planned features - -* **Messaging** A mathematical notation to support multi-language and multi-node communication for mathematical tasks. - -* **Array statistics** - -* **Integration** Univariate and multivariate integration framework. - -* **Probability and distributions** - -* **Fitting** Non-linear curve fitting facilities - -## Modules - -$modules - -## Multi-platform support - -KMath is developed as a multi-platform library, which means that most of the interfaces are declared in the -[common source sets](/kmath-core/src/commonMain) and implemented there wherever it is possible. In some cases, features -are delegated to platform-specific implementations even if they could be provided in the common module for performance -reasons. Currently, the Kotlin/JVM is the primary platform, however Kotlin/Native and Kotlin/JS contributions and -feedback are also welcome. - -## Performance - -Calculation performance is one of major goals of KMath in the future, but in some cases it is impossible to achieve -both performance and flexibility. - -We expect to focus on creating convenient universal API first and then work on increasing performance for specific -cases. We expect the worst KMath benchmarks will perform better than native Python, but worse than optimized -native/SciPy (mostly due to boxing operations on primitive numbers). The best performance of optimized parts could be -better than SciPy. - -### Repositories - -Release artifacts are accessible from bintray with following configuration (see documentation of -[Kotlin Multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html) for more details): - -```kotlin -repositories { - maven("https://dl.bintray.com/mipt-npm/kscience") -} - -dependencies { - api("kscience.kmath:kmath-core:$version") - // api("kscience.kmath:kmath-core-jvm:$version") for jvm-specific version -} -``` - -Gradle `6.0+` is required for multiplatform artifacts. - -#### Development - -Development builds are uploaded to the separate repository: - -```kotlin -repositories { - maven("https://dl.bintray.com/mipt-npm/dev") -} -``` - -## Contributing - -The project requires a lot of additional work. The most important thing we need is a feedback about what features are -required the most. Feel free to create feature requests. We are also welcome to code contributions, -especially in issues marked with -[waiting for a hero](https://github.com/mipt-npm/kmath/labels/waiting%20for%20a%20hero) label. diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index c079eaa84..73def3572 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -1,88 +1,64 @@ +import org.jetbrains.kotlin.allopen.gradle.AllOpenExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { java kotlin("jvm") - kotlin("plugin.allopen") - id("kotlinx.benchmark") + kotlin("plugin.allopen") version "1.3.72" + id("kotlinx.benchmark") version "0.2.0-dev-8" } -allOpen.annotation("org.openjdk.jmh.annotations.State") -sourceSets.register("benchmarks") +configure { + annotation("org.openjdk.jmh.annotations.State") +} repositories { - jcenter() - maven("https://clojars.org/repo") - maven("https://dl.bintray.com/egor-bogomolov/astminer/") - maven("https://dl.bintray.com/hotkeytlt/maven") - maven("https://dl.bintray.com/kotlin/kotlin-eap") - maven("https://dl.bintray.com/kotlin/kotlinx") + maven("http://dl.bintray.com/kyonifer/maven") + maven("https://dl.bintray.com/mipt-npm/scientifik") maven("https://dl.bintray.com/mipt-npm/dev") - maven("https://dl.bintray.com/mipt-npm/kscience") - maven("https://jitpack.io") - maven("http://logicrunch.research.it.uu.se/maven/") mavenCentral() } +sourceSets { + register("benchmarks") +} + dependencies { implementation(project(":kmath-ast")) - implementation(project(":kmath-kotlingrad")) implementation(project(":kmath-core")) implementation(project(":kmath-coroutines")) implementation(project(":kmath-commons")) - implementation(project(":kmath-stat")) + implementation(project(":kmath-prob")) + implementation(project(":kmath-koma")) implementation(project(":kmath-viktor")) implementation(project(":kmath-dimensions")) - implementation(project(":kmath-ejml")) - implementation(project(":kmath-nd4j")) - - implementation(project(":kmath-for-real")) - - implementation("org.deeplearning4j:deeplearning4j-core:1.0.0-beta7") - implementation("org.nd4j:nd4j-native:1.0.0-beta7") - -// uncomment if your system supports AVX2 -// val os = System.getProperty("os.name") -// -// if (System.getProperty("os.arch") in arrayOf("x86_64", "amd64")) when { -// os.startsWith("Windows") -> implementation("org.nd4j:nd4j-native:1.0.0-beta7:windows-x86_64-avx2") -// os == "Linux" -> implementation("org.nd4j:nd4j-native:1.0.0-beta7:linux-x86_64-avx2") -// os == "Mac OS X" -> implementation("org.nd4j:nd4j-native:1.0.0-beta7:macosx-x86_64-avx2") -// } else - implementation("org.nd4j:nd4j-native-platform:1.0.0-beta7") - - implementation("org.jetbrains.kotlinx:kotlinx-io:0.2.0-npm-dev-11") - implementation("org.jetbrains.kotlinx:kotlinx.benchmark.runtime:0.2.0-dev-20") - implementation("org.slf4j:slf4j-simple:1.7.30") - - // plotting - implementation("kscience.plotlykt:plotlykt-server:0.3.1-dev") - - "benchmarksImplementation"("org.jetbrains.kotlinx:kotlinx.benchmark.runtime-jvm:0.2.0-dev-20") - "benchmarksImplementation"(sourceSets.main.get().output + sourceSets.main.get().runtimeClasspath) + implementation("com.kyonifer:koma-core-ejml:0.12") + implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:0.2.0-npm-dev-6") + implementation("org.jetbrains.kotlinx:kotlinx.benchmark.runtime:0.2.0-dev-8") + "benchmarksCompile"(sourceSets.main.get().output + sourceSets.main.get().compileClasspath) //sourceSets.main.output + sourceSets.main.runtimeClasspath } // Configure benchmark benchmark { // Setup configurations - targets.register("benchmarks") - // This one matches sourceSet name above + targets { + // This one matches sourceSet name above + register("benchmarks") + } - configurations.register("fast") { - warmups = 1 // number of warmup iterations - iterations = 3 // number of iterations - iterationTime = 500 // time in seconds per iteration - iterationTimeUnit = "ms" // time unity for iterationTime, default is seconds + configurations { + register("fast") { + warmups = 5 // number of warmup iterations + iterations = 3 // number of iterations + iterationTime = 500 // time in seconds per iteration + iterationTimeUnit = "ms" // time unity for iterationTime, default is seconds + } } } -kotlin.sourceSets.all { - with(languageSettings) { - useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") - useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") - } -} tasks.withType { - kotlinOptions.jvmTarget = "11" -} + kotlinOptions { + jvmTarget = Scientifik.JVM_TARGET.toString() + } +} \ No newline at end of file diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ArrayBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ArrayBenchmark.kt deleted file mode 100644 index 8c44135fb..000000000 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ArrayBenchmark.kt +++ /dev/null @@ -1,38 +0,0 @@ -package kscience.kmath.benchmarks - -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import java.nio.IntBuffer - -@State(Scope.Benchmark) -internal class ArrayBenchmark { - @Benchmark - fun benchmarkArrayRead() { - var res = 0 - for (i in 1..size) res += array[size - i] - } - - @Benchmark - fun benchmarkBufferRead() { - var res = 0 - for (i in 1..size) res += arrayBuffer.get( - size - i - ) - } - - @Benchmark - fun nativeBufferRead() { - var res = 0 - for (i in 1..size) res += nativeBuffer.get( - size - i - ) - } - - companion object { - const val size: Int = 1000 - val array: IntArray = IntArray(size) { it } - val arrayBuffer: IntBuffer = IntBuffer.wrap(array) - val nativeBuffer: IntBuffer = IntBuffer.allocate(size).also { for (i in 0 until size) it.put(i, i) } - } -} diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt deleted file mode 100644 index ec8714617..000000000 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt +++ /dev/null @@ -1,50 +0,0 @@ -package kscience.kmath.linear - - -import kotlinx.benchmark.Benchmark -import kscience.kmath.commons.linear.CMMatrixContext -import kscience.kmath.commons.linear.CMMatrixContext.dot -import kscience.kmath.commons.linear.inverse -import kscience.kmath.commons.linear.toCM -import kscience.kmath.ejml.EjmlMatrixContext -import kscience.kmath.ejml.inverse -import kscience.kmath.ejml.toEjml -import kscience.kmath.operations.invoke -import kscience.kmath.structures.Matrix -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import kotlin.random.Random - -@State(Scope.Benchmark) -class LinearAlgebraBenchmark { - companion object { - val random = Random(1224) - val dim = 100 - - //creating invertible matrix - val u = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } - val l = Matrix.real(dim, dim) { i, j -> if (i >= j) random.nextDouble() else 0.0 } - val matrix = l dot u - } - - @Benchmark - fun kmathLUPInversion() { - MatrixContext.real.inverseWithLUP(matrix) - } - - @Benchmark - fun cmLUPInversion() { - CMMatrixContext { - val cm = matrix.toCM() //avoid overhead on conversion - inverse(cm) - } - } - - @Benchmark - fun ejmlInverse() { - EjmlMatrixContext { - val km = matrix.toEjml() //avoid overhead on conversion - inverse(km) - } - } -} \ No newline at end of file diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/MultiplicationBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/MultiplicationBenchmark.kt deleted file mode 100644 index 9d2b02245..000000000 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/MultiplicationBenchmark.kt +++ /dev/null @@ -1,60 +0,0 @@ -package kscience.kmath.benchmarks - -import kotlinx.benchmark.Benchmark -import kscience.kmath.commons.linear.CMMatrixContext -import kscience.kmath.commons.linear.CMMatrixContext.dot -import kscience.kmath.commons.linear.toCM -import kscience.kmath.ejml.EjmlMatrixContext -import kscience.kmath.ejml.toEjml -import kscience.kmath.linear.real -import kscience.kmath.operations.invoke -import kscience.kmath.structures.Matrix -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State -import kotlin.random.Random - -@State(Scope.Benchmark) -class MultiplicationBenchmark { - companion object { - val random = Random(12224) - val dim = 1000 - - //creating invertible matrix - val matrix1 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } - val matrix2 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } - - val cmMatrix1 = matrix1.toCM() - val cmMatrix2 = matrix2.toCM() - - val ejmlMatrix1 = matrix1.toEjml() - val ejmlMatrix2 = matrix2.toEjml() - } - - @Benchmark - fun commonsMathMultiplication() { - CMMatrixContext.invoke { - cmMatrix1 dot cmMatrix2 - } - } - - @Benchmark - fun ejmlMultiplication() { - EjmlMatrixContext.invoke { - ejmlMatrix1 dot ejmlMatrix2 - } - } - - @Benchmark - fun ejmlMultiplicationwithConversion() { - val ejmlMatrix1 = matrix1.toEjml() - val ejmlMatrix2 = matrix2.toEjml() - EjmlMatrixContext.invoke { - ejmlMatrix1 dot ejmlMatrix2 - } - } - - @Benchmark - fun bufferedMultiplication() { - matrix1 dot matrix2 - } -} \ No newline at end of file diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/NDFieldBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/NDFieldBenchmark.kt deleted file mode 100644 index 1be8e7236..000000000 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/NDFieldBenchmark.kt +++ /dev/null @@ -1,50 +0,0 @@ -package kscience.kmath.benchmarks - -import kscience.kmath.operations.RealField -import kscience.kmath.operations.invoke -import kscience.kmath.structures.* -import org.openjdk.jmh.annotations.Benchmark -import org.openjdk.jmh.annotations.Scope -import org.openjdk.jmh.annotations.State - -@State(Scope.Benchmark) -internal class NDFieldBenchmark { - @Benchmark - fun autoFieldAdd() { - bufferedField { - var res: NDBuffer = one - repeat(n) { res += one } - } - } - - @Benchmark - fun autoElementAdd() { - var res = genericField.one - repeat(n) { res += 1.0 } - } - - @Benchmark - fun specializedFieldAdd() { - specializedField { - var res: NDBuffer = one - repeat(n) { res += 1.0 } - } - } - - - @Benchmark - fun boxingFieldAdd() { - genericField { - var res: NDBuffer = one - repeat(n) { res += one } - } - } - - companion object { - const val dim: Int = 1000 - const val n: Int = 100 - val bufferedField: BufferedNDField = NDField.auto(RealField, dim, dim) - val specializedField: RealNDField = NDField.real(dim, dim) - val genericField: BoxingNDField = NDField.boxing(RealField, dim, dim) - } -} \ No newline at end of file diff --git a/examples/src/benchmarks/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt new file mode 100644 index 000000000..d605e1b9c --- /dev/null +++ b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt @@ -0,0 +1,48 @@ +package scientifik.kmath.structures + +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.State +import java.nio.IntBuffer + + +@State(Scope.Benchmark) +class ArrayBenchmark { + + @Benchmark + fun benchmarkArrayRead() { + var res = 0 + for (i in 1..size) { + res += array[size - i] + } + } + + @Benchmark + fun benchmarkBufferRead() { + var res = 0 + for (i in 1..size) { + res += arrayBuffer.get(size - i) + } + } + + @Benchmark + fun nativeBufferRead() { + var res = 0 + for (i in 1..size) { + res += nativeBuffer.get(size - i) + } + } + + companion object { + val size = 1000 + + val array = IntArray(size) { it } + val arrayBuffer = IntBuffer.wrap(array) + val nativeBuffer = IntBuffer.allocate(size).also { + for (i in 0 until size) { + it.put(i, i) + } + + } + } +} \ No newline at end of file diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/BufferBenchmark.kt b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/BufferBenchmark.kt similarity index 50% rename from examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/BufferBenchmark.kt rename to examples/src/benchmarks/kotlin/scientifik/kmath/structures/BufferBenchmark.kt index 4c64517f1..e40b0c4b7 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/BufferBenchmark.kt +++ b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/BufferBenchmark.kt @@ -1,18 +1,17 @@ -package kscience.kmath.benchmarks +package scientifik.kmath.structures -import kscience.kmath.operations.Complex -import kscience.kmath.operations.complex -import kscience.kmath.structures.MutableBuffer -import kscience.kmath.structures.RealBuffer import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.State +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.complex @State(Scope.Benchmark) -internal class BufferBenchmark { +class BufferBenchmark { + @Benchmark fun genericRealBufferReadWrite() { - val buffer = RealBuffer(size) { it.toDouble() } + val buffer = RealBuffer(size){it.toDouble()} (0 until size).forEach { buffer[it] @@ -21,7 +20,7 @@ internal class BufferBenchmark { @Benchmark fun complexBufferReadWrite() { - val buffer = MutableBuffer.complex(size / 2) { Complex(it.toDouble(), -it.toDouble()) } + val buffer = MutableBuffer.complex(size / 2){Complex(it.toDouble(), -it.toDouble())} (0 until size / 2).forEach { buffer[it] @@ -29,6 +28,6 @@ internal class BufferBenchmark { } companion object { - const val size: Int = 100 + const val size = 100 } } \ No newline at end of file diff --git a/examples/src/benchmarks/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt new file mode 100644 index 000000000..ae27620f7 --- /dev/null +++ b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -0,0 +1,58 @@ +package scientifik.kmath.structures + +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.State +import scientifik.kmath.operations.RealField + +@State(Scope.Benchmark) +class NDFieldBenchmark { + + @Benchmark + fun autoFieldAdd() { + bufferedField.run { + var res: NDBuffer = one + repeat(n) { + res += one + } + } + } + + @Benchmark + fun autoElementAdd() { + var res = genericField.one + repeat(n) { + res += 1.0 + } + } + + @Benchmark + fun specializedFieldAdd() { + specializedField.run { + var res: NDBuffer = one + repeat(n) { + res += 1.0 + } + } + } + + + @Benchmark + fun boxingFieldAdd() { + genericField.run { + var res: NDBuffer = one + repeat(n) { + res += one + } + } + } + + companion object { + val dim = 1000 + val n = 100 + + val bufferedField = NDField.auto(RealField, dim, dim) + val specializedField = NDField.real(dim, dim) + val genericField = NDField.boxing(RealField, dim, dim) + } +} \ No newline at end of file diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt similarity index 55% rename from examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt rename to examples/src/benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt index 8663e353c..f7b9661ef 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt +++ b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt @@ -1,29 +1,27 @@ -package kscience.kmath.benchmarks +package scientifik.kmath.structures -import kscience.kmath.operations.RealField -import kscience.kmath.operations.invoke -import kscience.kmath.structures.BufferedNDField -import kscience.kmath.structures.NDField -import kscience.kmath.structures.RealNDField -import kscience.kmath.viktor.ViktorNDField import org.jetbrains.bio.viktor.F64Array import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.State +import scientifik.kmath.operations.RealField +import scientifik.kmath.viktor.ViktorNDField + @State(Scope.Benchmark) -internal class ViktorBenchmark { - final val dim: Int = 1000 - final val n: Int = 100 +class ViktorBenchmark { + final val dim = 1000 + final val n = 100 // automatically build context most suited for given type. - final val autoField: BufferedNDField = NDField.auto(RealField, dim, dim) - final val realField: RealNDField = NDField.real(dim, dim) - final val viktorField: ViktorNDField = ViktorNDField(intArrayOf(dim, dim)) + final val autoField = NDField.auto(RealField, dim, dim) + final val realField = NDField.real(dim, dim) + + final val viktorField = ViktorNDField(intArrayOf(dim, dim)) @Benchmark fun automaticFieldAddition() { - autoField { + autoField.run { var res = one repeat(n) { res += one } } @@ -31,7 +29,7 @@ internal class ViktorBenchmark { @Benchmark fun viktorFieldAddition() { - viktorField { + viktorField.run { var res = one repeat(n) { res += one } } @@ -39,14 +37,14 @@ internal class ViktorBenchmark { @Benchmark fun rawViktor() { - val one = F64Array.full(init = 1.0, shape = intArrayOf(dim, dim)) + val one = F64Array.full(init = 1.0, shape = *intArrayOf(dim, dim)) var res = one repeat(n) { res = res + one } } @Benchmark - fun realFieldLog() { - realField { + fun realdFieldLog() { + realField.run { val fortyTwo = produce { 42.0 } var res = one repeat(n) { res = ln(fortyTwo) } diff --git a/examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt b/examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt new file mode 100644 index 000000000..6ec9e9c17 --- /dev/null +++ b/examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt @@ -0,0 +1,8 @@ +package scientifik.kmath.utils + +import kotlin.system.measureTimeMillis + +internal inline fun measureAndPrint(title: String, block: () -> Unit) { + val time = measureTimeMillis(block) + println("$title completed in $time millis") +} \ No newline at end of file diff --git a/examples/src/main/kotlin/kscience/kmath/ast/KotlingradSupport.kt b/examples/src/main/kotlin/kscience/kmath/ast/KotlingradSupport.kt deleted file mode 100644 index b3c827503..000000000 --- a/examples/src/main/kotlin/kscience/kmath/ast/KotlingradSupport.kt +++ /dev/null @@ -1,24 +0,0 @@ -package kscience.kmath.ast - -import kscience.kmath.asm.compile -import kscience.kmath.expressions.derivative -import kscience.kmath.expressions.invoke -import kscience.kmath.expressions.symbol -import kscience.kmath.kotlingrad.differentiable -import kscience.kmath.operations.RealField - -/** - * In this example, x^2-4*x-44 function is differentiated with Kotlin∇, and the autodiff result is compared with - * valid derivative. - */ -fun main() { - val x by symbol - - val actualDerivative = MstExpression(RealField, "x^2-4*x-44".parseMath()) - .differentiable() - .derivative(x) - .compile() - - val expectedDerivative = MstExpression(RealField, "2*x-4".parseMath()).compile() - assert(actualDerivative("x" to 123.0) == expectedDerivative("x" to 123.0)) -} diff --git a/examples/src/main/kotlin/kscience/kmath/commons/fit/fitWithAutoDiff.kt b/examples/src/main/kotlin/kscience/kmath/commons/fit/fitWithAutoDiff.kt deleted file mode 100644 index c0cd9dc5c..000000000 --- a/examples/src/main/kotlin/kscience/kmath/commons/fit/fitWithAutoDiff.kt +++ /dev/null @@ -1,102 +0,0 @@ -package kscience.kmath.commons.fit - -import kotlinx.html.br -import kotlinx.html.h3 -import kscience.kmath.commons.optimization.chiSquared -import kscience.kmath.commons.optimization.minimize -import kscience.kmath.expressions.symbol -import kscience.kmath.real.RealVector -import kscience.kmath.real.map -import kscience.kmath.real.step -import kscience.kmath.stat.* -import kscience.kmath.structures.asIterable -import kscience.kmath.structures.toList -import kscience.plotly.* -import kscience.plotly.models.ScatterMode -import kscience.plotly.models.TraceValues -import kotlin.math.pow -import kotlin.math.sqrt - -//Forward declaration of symbols that will be used in expressions. -// This declaration is required for -private val a by symbol -private val b by symbol -private val c by symbol - -/** - * Shortcut to use buffers in plotly - */ -operator fun TraceValues.invoke(vector: RealVector) { - numbers = vector.asIterable() -} - -/** - * Least squares fie with auto-differentiation. Uses `kmath-commons` and `kmath-for-real` modules. - */ -fun main() { - - //A generator for a normally distributed values - val generator = Distribution.normal() - - //A chain/flow of random values with the given seed - val chain = generator.sample(RandomGenerator.default(112667)) - - - //Create a uniformly distributed x values like numpy.arrange - val x = 1.0..100.0 step 1.0 - - - //Perform an operation on each x value (much more effective, than numpy) - val y = x.map { - val value = it.pow(2) + it + 1 - value + chain.nextDouble() * sqrt(value) - } - // this will also work, but less effective: - // val y = x.pow(2)+ x + 1 + chain.nextDouble() - - // create same errors for all xs - val yErr = y.map { sqrt(it) }//RealVector.same(x.size, sigma) - - // compute differentiable chi^2 sum for given model ax^2 + bx + c - val chi2 = Fitting.chiSquared(x, y, yErr) { x1 -> - //bind variables to autodiff context - val a = bind(a) - val b = bind(b) - //Include default value for c if it is not provided as a parameter - val c = bindOrNull(c) ?: one - a * x1.pow(2) + b * x1 + c - } - - //minimize the chi^2 in given starting point. Derivatives are not required, they are already included. - val result: OptimizationResult = chi2.minimize(a to 1.5, b to 0.9, c to 1.0) - - //display a page with plot and numerical results - val page = Plotly.page { - plot { - scatter { - mode = ScatterMode.markers - x(x) - y(y) - error_y { - array = yErr.toList() - } - name = "data" - } - scatter { - mode = ScatterMode.lines - x(x) - y(x.map { result.point[a]!! * it.pow(2) + result.point[b]!! * it + 1 }) - name = "fit" - } - } - br() - h3{ - +"Fit result: $result" - } - h3{ - +"Chi2/dof = ${result.value / (x.size - 3)}" - } - } - - page.makeFile() -} \ No newline at end of file diff --git a/examples/src/main/kotlin/kscience/kmath/operations/BigIntDemo.kt b/examples/src/main/kotlin/kscience/kmath/operations/BigIntDemo.kt deleted file mode 100644 index 0e9811ff8..000000000 --- a/examples/src/main/kotlin/kscience/kmath/operations/BigIntDemo.kt +++ /dev/null @@ -1,6 +0,0 @@ -package kscience.kmath.operations - -fun main() { - val res = BigIntField { number(1) * 2 } - println("bigint:$res") -} \ No newline at end of file diff --git a/examples/src/main/kotlin/kscience/kmath/operations/ComplexDemo.kt b/examples/src/main/kotlin/kscience/kmath/operations/ComplexDemo.kt deleted file mode 100644 index e84fd8df3..000000000 --- a/examples/src/main/kotlin/kscience/kmath/operations/ComplexDemo.kt +++ /dev/null @@ -1,23 +0,0 @@ -package kscience.kmath.operations - -import kscience.kmath.structures.NDElement -import kscience.kmath.structures.NDField -import kscience.kmath.structures.complex - -fun main() { - // 2d element - val element = NDElement.complex(2, 2) { (i,j) -> - Complex(i.toDouble() - j.toDouble(), i.toDouble() + j.toDouble()) - } - println(element) - - // 1d element operation - val result = with(NDField.complex(8)) { - val a = produce { (it) -> i * it - it.toDouble() } - val b = 3 - val c = Complex(1.0, 1.0) - - (a pow b) + c - } - println(result) -} diff --git a/examples/src/main/kotlin/kscience/kmath/structures/typeSafeDimensions.kt b/examples/src/main/kotlin/kscience/kmath/structures/typeSafeDimensions.kt deleted file mode 100644 index 987eea16f..000000000 --- a/examples/src/main/kotlin/kscience/kmath/structures/typeSafeDimensions.kt +++ /dev/null @@ -1,31 +0,0 @@ -package kscience.kmath.structures - -import kscience.kmath.dimensions.D2 -import kscience.kmath.dimensions.D3 -import kscience.kmath.dimensions.DMatrixContext -import kscience.kmath.dimensions.Dimension -import kscience.kmath.operations.RealField - -private fun DMatrixContext.simple() { - val m1 = produce { i, j -> (i + j).toDouble() } - val m2 = produce { i, j -> (i + j).toDouble() } - - //Dimension-safe addition - m1.transpose() + m2 -} - -private object D5 : Dimension { - override val dim: UInt = 5u -} - -private fun DMatrixContext.custom() { - val m1 = produce { i, j -> (i + j).toDouble() } - val m2 = produce { i, j -> (i - j).toDouble() } - val m3 = produce { i, j -> (i - j).toDouble() } - (m1 dot m2) + m3 -} - -fun main(): Unit = with(DMatrixContext.real) { - simple() - custom() -} diff --git a/examples/src/main/kotlin/kscience/kmath/ast/ExpressionsInterpretersBenchmark.kt b/examples/src/main/kotlin/scientifik/kmath/ast/ExpressionsInterpretersBenchmark.kt similarity index 66% rename from examples/src/main/kotlin/kscience/kmath/ast/ExpressionsInterpretersBenchmark.kt rename to examples/src/main/kotlin/scientifik/kmath/ast/ExpressionsInterpretersBenchmark.kt index a4806ed68..17a70a4aa 100644 --- a/examples/src/main/kotlin/kscience/kmath/ast/ExpressionsInterpretersBenchmark.kt +++ b/examples/src/main/kotlin/scientifik/kmath/ast/ExpressionsInterpretersBenchmark.kt @@ -1,19 +1,19 @@ -package kscience.kmath.ast +package scientifik.kmath.ast -import kscience.kmath.asm.compile -import kscience.kmath.expressions.Expression -import kscience.kmath.expressions.expressionInField -import kscience.kmath.expressions.invoke -import kscience.kmath.operations.Field -import kscience.kmath.operations.RealField +import scientifik.kmath.asm.compile +import scientifik.kmath.expressions.Expression +import scientifik.kmath.expressions.expressionInField +import scientifik.kmath.expressions.invoke +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.RealField import kotlin.random.Random import kotlin.system.measureTimeMillis -internal class ExpressionsInterpretersBenchmark { +class ExpressionsInterpretersBenchmark { private val algebra: Field = RealField fun functionalExpression() { val expr = algebra.expressionInField { - symbol("x") * const(2.0) + const(2.0) / symbol("x") - const(16.0) + variable("x") * const(2.0) + const(2.0) / variable("x") - const(16.0) } invokeAndSum(expr) @@ -47,16 +47,6 @@ internal class ExpressionsInterpretersBenchmark { } } -/** - * This benchmark compares basically evaluation of simple function with MstExpression interpreter, ASM backend and - * core FunctionalExpressions API. - * - * The expected rating is: - * - * 1. ASM. - * 2. MST. - * 3. FE. - */ fun main() { val benchmark = ExpressionsInterpretersBenchmark() diff --git a/examples/src/main/kotlin/kscience/kmath/stat/DistributionBenchmark.kt b/examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionBenchmark.kt similarity index 85% rename from examples/src/main/kotlin/kscience/kmath/stat/DistributionBenchmark.kt rename to examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionBenchmark.kt index ef554aeff..b060cddb6 100644 --- a/examples/src/main/kotlin/kscience/kmath/stat/DistributionBenchmark.kt +++ b/examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionBenchmark.kt @@ -1,23 +1,26 @@ -package kscience.kmath.commons.prob +package scientifik.kmath.commons.prob import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking -import kscience.kmath.chains.BlockingRealChain -import kscience.kmath.stat.* import org.apache.commons.rng.sampling.distribution.ZigguratNormalizedGaussianSampler import org.apache.commons.rng.simple.RandomSource +import scientifik.kmath.chains.BlockingRealChain +import scientifik.kmath.prob.* import java.time.Duration import java.time.Instant -private fun runChain(): Duration { + +private suspend fun runChain(): Duration { val generator = RandomGenerator.fromSource(RandomSource.MT, 123L) + val normal = Distribution.normal(NormalSamplerMethod.Ziggurat) val chain = normal.sample(generator) as BlockingRealChain + val startTime = Instant.now() var sum = 0.0 - repeat(10000001) { counter -> + sum += chain.nextDouble() if (counter % 100000 == 0) { @@ -26,7 +29,6 @@ private fun runChain(): Duration { println("Chain sampler completed $counter elements in $duration: $meanValue") } } - return Duration.between(startTime, Instant.now()) } @@ -34,9 +36,10 @@ private fun runDirect(): Duration { val provider = RandomSource.create(RandomSource.MT, 123L) val sampler = ZigguratNormalizedGaussianSampler(provider) val startTime = Instant.now() - var sum = 0.0 + var sum = 0.0 repeat(10000001) { counter -> + sum += sampler.sample() if (counter % 100000 == 0) { @@ -45,7 +48,6 @@ private fun runDirect(): Duration { println("Direct sampler completed $counter elements in $duration: $meanValue") } } - return Duration.between(startTime, Instant.now()) } @@ -54,9 +56,16 @@ private fun runDirect(): Duration { */ fun main() { runBlocking(Dispatchers.Default) { - val chainJob = async { runChain() } - val directJob = async { runDirect() } + val chainJob = async { + runChain() + } + + val directJob = async { + runDirect() + } + println("Chain: ${chainJob.await()}") println("Direct: ${directJob.await()}") } -} + +} \ No newline at end of file diff --git a/examples/src/main/kotlin/kscience/kmath/stat/DistributionDemo.kt b/examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionDemo.kt similarity index 52% rename from examples/src/main/kotlin/kscience/kmath/stat/DistributionDemo.kt rename to examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionDemo.kt index 24a4cb1a7..e059415dc 100644 --- a/examples/src/main/kotlin/kscience/kmath/stat/DistributionDemo.kt +++ b/examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionDemo.kt @@ -1,18 +1,15 @@ -package kscience.kmath.stat +package scientifik.kmath.commons.prob import kotlinx.coroutines.runBlocking -import kscience.kmath.chains.Chain -import kscience.kmath.chains.collectWithState +import scientifik.kmath.chains.Chain +import scientifik.kmath.chains.collectWithState +import scientifik.kmath.prob.Distribution +import scientifik.kmath.prob.RandomGenerator +import scientifik.kmath.prob.normal -/** - * The state of distribution averager - */ -private data class AveragingChainState(var num: Int = 0, var value: Double = 0.0) +data class AveragingChainState(var num: Int = 0, var value: Double = 0.0) -/** - * Averaging - */ -private fun Chain.mean(): Chain = collectWithState(AveragingChainState(), { it.copy() }) { chain -> +fun Chain.mean(): Chain = collectWithState(AveragingChainState(), { it.copy() }) { chain -> val next = chain.next() num++ value += next diff --git a/examples/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/examples/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt new file mode 100644 index 000000000..960f03de3 --- /dev/null +++ b/examples/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -0,0 +1,65 @@ +package scientifik.kmath.linear + +import koma.matrix.ejml.EJMLMatrixFactory +import scientifik.kmath.commons.linear.CMMatrixContext +import scientifik.kmath.commons.linear.inverse +import scientifik.kmath.commons.linear.toCM +import scientifik.kmath.operations.RealField +import scientifik.kmath.structures.Matrix +import kotlin.contracts.ExperimentalContracts +import kotlin.random.Random +import kotlin.system.measureTimeMillis + +@ExperimentalContracts +fun main() { + val random = Random(1224) + val dim = 100 + //creating invertible matrix + val u = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + val l = Matrix.real(dim, dim) { i, j -> if (i >= j) random.nextDouble() else 0.0 } + val matrix = l dot u + + val n = 5000 // iterations + + MatrixContext.real.run { + + repeat(50) { + val res = inverse(matrix) + } + + val inverseTime = measureTimeMillis { + repeat(n) { + val res = inverse(matrix) + } + } + + println("[kmath] Inversion of $n matrices $dim x $dim finished in $inverseTime millis") + } + + //commons-math + + val commonsTime = measureTimeMillis { + CMMatrixContext.run { + val cm = matrix.toCM() //avoid overhead on conversion + repeat(n) { + val res = inverse(cm) + } + } + } + + + println("[commons-math] Inversion of $n matrices $dim x $dim finished in $commonsTime millis") + + //koma-ejml + + val komaTime = measureTimeMillis { + KomaMatrixContext(EJMLMatrixFactory(), RealField).run { + val km = matrix.toKoma() //avoid overhead on conversion + repeat(n) { + val res = inverse(km) + } + } + } + + println("[koma-ejml] Inversion of $n matrices $dim x $dim finished in $komaTime millis") +} \ No newline at end of file diff --git a/examples/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt b/examples/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt new file mode 100644 index 000000000..03bd0001c --- /dev/null +++ b/examples/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt @@ -0,0 +1,49 @@ +package scientifik.kmath.linear + +import koma.matrix.ejml.EJMLMatrixFactory +import scientifik.kmath.commons.linear.CMMatrixContext +import scientifik.kmath.commons.linear.toCM +import scientifik.kmath.operations.RealField +import scientifik.kmath.structures.Matrix +import kotlin.random.Random +import kotlin.system.measureTimeMillis + +fun main() { + val random = Random(12224) + val dim = 1000 + //creating invertible matrix + val matrix1 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + val matrix2 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + +// //warmup +// matrix1 dot matrix2 + + CMMatrixContext.run { + val cmMatrix1 = matrix1.toCM() + val cmMatrix2 = matrix2.toCM() + + val cmTime = measureTimeMillis { + cmMatrix1 dot cmMatrix2 + } + + println("CM implementation time: $cmTime") + } + + + KomaMatrixContext(EJMLMatrixFactory(), RealField).run { + val komaMatrix1 = matrix1.toKoma() + val komaMatrix2 = matrix2.toKoma() + + val komaTime = measureTimeMillis { + komaMatrix1 dot komaMatrix2 + } + + println("Koma-ejml implementation time: $komaTime") + } + + val genericTime = measureTimeMillis { + val res = matrix1 dot matrix2 + } + + println("Generic implementation time: $genericTime") +} \ No newline at end of file diff --git a/examples/src/main/kotlin/scientifik/kmath/operations/ComplexDemo.kt b/examples/src/main/kotlin/scientifik/kmath/operations/ComplexDemo.kt new file mode 100644 index 000000000..4841f9dd8 --- /dev/null +++ b/examples/src/main/kotlin/scientifik/kmath/operations/ComplexDemo.kt @@ -0,0 +1,21 @@ +package scientifik.kmath.operations + +import scientifik.kmath.structures.NDElement +import scientifik.kmath.structures.NDField +import scientifik.kmath.structures.complex + +fun main() { + val element = NDElement.complex(2, 2) { index: IntArray -> + Complex(index[0].toDouble() - index[1].toDouble(), index[0].toDouble() + index[1].toDouble()) + } + + + val compute = NDField.complex(8).run { + val a = produce { (it) -> i * it - it.toDouble() } + val b = 3 + val c = Complex(1.0, 1.0) + + (a pow b) + c + } + +} \ No newline at end of file diff --git a/examples/src/main/kotlin/kscience/kmath/structures/ComplexND.kt b/examples/src/main/kotlin/scientifik/kmath/structures/ComplexND.kt similarity index 73% rename from examples/src/main/kotlin/kscience/kmath/structures/ComplexND.kt rename to examples/src/main/kotlin/scientifik/kmath/structures/ComplexND.kt index aa4b10ef2..991cd34a1 100644 --- a/examples/src/main/kotlin/kscience/kmath/structures/ComplexND.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/ComplexND.kt @@ -1,9 +1,9 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.linear.transpose -import kscience.kmath.operations.Complex -import kscience.kmath.operations.ComplexField -import kscience.kmath.operations.invoke +import scientifik.kmath.linear.transpose +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.ComplexField +import scientifik.kmath.operations.invoke import kotlin.system.measureTimeMillis fun main() { @@ -13,8 +13,9 @@ fun main() { val realField = NDField.real(dim, dim) val complexField = NDField.complex(dim, dim) + val realTime = measureTimeMillis { - realField { + realField.run { var res: NDBuffer = one repeat(n) { res += 1.0 @@ -25,15 +26,18 @@ fun main() { println("Real addition completed in $realTime millis") val complexTime = measureTimeMillis { - complexField { + complexField.run { var res: NDBuffer = one - repeat(n) { res += 1.0 } + repeat(n) { + res += 1.0 + } } } println("Complex addition completed in $complexTime millis") } + fun complexExample() { //Create a context for 2-d structure with complex values ComplexField { @@ -42,7 +46,10 @@ fun complexExample() { val x = one * 2.5 operator fun Number.plus(other: Complex) = Complex(this.toDouble() + other.re, other.im) //a structure generator specific to this context - val matrix = produce { (k, l) -> k + l * i } + val matrix = produce { (k, l) -> + k + l * i + } + //Perform sum val sum = matrix + x + 1.0 diff --git a/examples/src/main/kotlin/kscience/kmath/structures/NDField.kt b/examples/src/main/kotlin/scientifik/kmath/structures/NDField.kt similarity index 61% rename from examples/src/main/kotlin/kscience/kmath/structures/NDField.kt rename to examples/src/main/kotlin/scientifik/kmath/structures/NDField.kt index e53af0dee..2aafb504d 100644 --- a/examples/src/main/kotlin/kscience/kmath/structures/NDField.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/NDField.kt @@ -1,23 +1,16 @@ -package kscience.kmath.structures +package scientifik.kmath.structures import kotlinx.coroutines.GlobalScope -import kscience.kmath.nd4j.Nd4jArrayField -import kscience.kmath.operations.RealField -import kscience.kmath.operations.invoke -import org.nd4j.linalg.factory.Nd4j -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract +import scientifik.kmath.operations.RealField import kotlin.system.measureTimeMillis internal inline fun measureAndPrint(title: String, block: () -> Unit) { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } val time = measureTimeMillis(block) println("$title completed in $time millis") } + fun main() { - // initializing Nd4j - Nd4j.zeros(0) val dim = 1000 val n = 1000 @@ -27,32 +20,29 @@ fun main() { val specializedField = NDField.real(dim, dim) //A generic boxing field. It should be used for objects, not primitives. val genericField = NDField.boxing(RealField, dim, dim) - // Nd4j specialized field. - val nd4jField = Nd4jArrayField.real(dim, dim) measureAndPrint("Automatic field addition") { - autoField { + autoField.run { var res: NDBuffer = one - repeat(n) { res += number(1.0) } + repeat(n) { + res += number(1.0) + } } } measureAndPrint("Element addition") { var res = genericField.one - repeat(n) { res += 1.0 } - } - - measureAndPrint("Specialized addition") { - specializedField { - var res: NDBuffer = one - repeat(n) { res += 1.0 } + repeat(n) { + res += 1.0 } } - measureAndPrint("Nd4j specialized addition") { - nd4jField { - var res = one - repeat(n) { res += 1.0 as Number } + measureAndPrint("Specialized addition") { + specializedField.run { + var res: NDBuffer = one + repeat(n) { + res += 1.0 + } } } @@ -70,11 +60,12 @@ fun main() { measureAndPrint("Generic addition") { //genericField.run(action) - genericField { + genericField.run { var res: NDBuffer = one repeat(n) { - res += one // couldn't avoid using `one` due to resolution ambiguity } + res += one // con't avoid using `one` due to resolution ambiguity } } } -} + +} \ No newline at end of file diff --git a/examples/src/main/kotlin/kscience/kmath/structures/StructureReadBenchmark.kt b/examples/src/main/kotlin/scientifik/kmath/structures/StructureReadBenchmark.kt similarity index 81% rename from examples/src/main/kotlin/kscience/kmath/structures/StructureReadBenchmark.kt rename to examples/src/main/kotlin/scientifik/kmath/structures/StructureReadBenchmark.kt index 51fd4f956..a33fdb2c4 100644 --- a/examples/src/main/kotlin/kscience/kmath/structures/StructureReadBenchmark.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/StructureReadBenchmark.kt @@ -1,33 +1,35 @@ -package kscience.kmath.structures +package scientifik.kmath.structures import kotlin.system.measureTimeMillis -fun main() { +fun main(args: Array) { val n = 6000 + val array = DoubleArray(n * n) { 1.0 } val buffer = RealBuffer(array) val strides = DefaultStrides(intArrayOf(n, n)) + val structure = BufferNDStructure(strides, buffer) measureTimeMillis { - var res = 0.0 + var res: Double = 0.0 strides.indices().forEach { res = structure[it] } } // warmup val time1 = measureTimeMillis { - var res = 0.0 + var res: Double = 0.0 strides.indices().forEach { res = structure[it] } } println("Structure reading finished in $time1 millis") val time2 = measureTimeMillis { - var res = 0.0 + var res: Double = 0.0 strides.indices().forEach { res = buffer[strides.offset(it)] } } println("Buffer reading finished in $time2 millis") val time3 = measureTimeMillis { - var res = 0.0 + var res: Double = 0.0 strides.indices().forEach { res = array[strides.offset(it)] } } println("Array reading finished in $time3 millis") diff --git a/examples/src/main/kotlin/kscience/kmath/structures/StructureWriteBenchmark.kt b/examples/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt similarity index 73% rename from examples/src/main/kotlin/kscience/kmath/structures/StructureWriteBenchmark.kt rename to examples/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt index db55b454f..0241f12ad 100644 --- a/examples/src/main/kotlin/kscience/kmath/structures/StructureWriteBenchmark.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt @@ -1,20 +1,29 @@ -package kscience.kmath.structures +package scientifik.kmath.structures import kotlin.system.measureTimeMillis -fun main() { + +fun main(args: Array) { + val n = 6000 + val structure = NDStructure.build(intArrayOf(n, n), Buffer.Companion::auto) { 1.0 } + structure.mapToBuffer { it + 1 } // warm-up - val time1 = measureTimeMillis { val res = structure.mapToBuffer { it + 1 } } + + val time1 = measureTimeMillis { + val res = structure.mapToBuffer { it + 1 } + } println("Structure mapping finished in $time1 millis") + val array = DoubleArray(n * n) { 1.0 } val time2 = measureTimeMillis { val target = DoubleArray(n * n) - val res = array.forEachIndexed { index, value -> target[index] = value + 1 } + val res = array.forEachIndexed { index, value -> + target[index] = value + 1 + } } - println("Array mapping finished in $time2 millis") val buffer = RealBuffer(DoubleArray(n * n) { 1.0 }) diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt new file mode 100644 index 000000000..fdc09ed5d --- /dev/null +++ b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt @@ -0,0 +1,35 @@ +package scientifik.kmath.structures + +import scientifik.kmath.dimensions.D2 +import scientifik.kmath.dimensions.D3 +import scientifik.kmath.dimensions.DMatrixContext +import scientifik.kmath.dimensions.Dimension +import scientifik.kmath.operations.RealField + +fun DMatrixContext.simple() { + val m1 = produce { i, j -> (i + j).toDouble() } + val m2 = produce { i, j -> (i + j).toDouble() } + + //Dimension-safe addition + m1.transpose() + m2 +} + + +object D5 : Dimension { + override val dim: UInt = 5u +} + +fun DMatrixContext.custom() { + val m1 = produce { i, j -> (i + j).toDouble() } + val m2 = produce { i, j -> (i - j).toDouble() } + val m3 = produce { i, j -> (i - j).toDouble() } + + (m1 dot m2) + m3 +} + +fun main() { + DMatrixContext.real.run { + simple() + custom() + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 930bba550..000000000 --- a/gradle.properties +++ /dev/null @@ -1,9 +0,0 @@ -kotlin.code.style=official -kotlin.parallel.tasks.in.project=true -kotlin.mpp.enableGranularSourceSetsMetadata=true -kotlin.native.enableDependencyPropagation=false -kotlin.mpp.stability.nowarn=true - -org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m -org.gradle.parallel=true -systemProp.org.gradle.internal.publish.checksums.insecure=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c02..62d4c0535 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index be52383ef..bb8b2fc26 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c8..fbd7c5158 100755 --- a/gradlew +++ b/gradlew @@ -130,7 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index 107acd32c..5093609d5 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto execute +if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,6 +64,21 @@ echo location of your Java installation. goto fail +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + :execute @rem Setup the command line @@ -71,7 +86,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell diff --git a/kmath-ast/README.md b/kmath-ast/README.md index 043224800..2339d0426 100644 --- a/kmath-ast/README.md +++ b/kmath-ast/README.md @@ -8,32 +8,32 @@ This subproject implements the following features: - Evaluating expressions by traversing MST. > #### Artifact: -> This module is distributed in the artifact `kscience.kmath:kmath-ast:0.1.4-dev-8`. +> This module is distributed in the artifact `scientifik:kmath-ast:0.1.4-dev-8`. > > **Gradle:** > > ```gradle > repositories { -> maven { url 'https://dl.bintray.com/mipt-npm/kscience' } +> maven { url 'https://dl.bintray.com/mipt-npm/scientifik' } > maven { url 'https://dl.bintray.com/mipt-npm/dev' } > maven { url https://dl.bintray.com/hotkeytlt/maven' } > } > > dependencies { -> implementation 'kscience.kmath:kmath-ast:0.1.4-dev-8' +> implementation 'scientifik:kmath-ast:0.1.4-dev-8' > } > ``` > **Gradle Kotlin DSL:** > > ```kotlin > repositories { -> maven("https://dl.bintray.com/mipt-npm/kscience") +> maven("https://dl.bintray.com/mipt-npm/scientifik") > maven("https://dl.bintray.com/mipt-npm/dev") > maven("https://dl.bintray.com/hotkeytlt/maven") > } > > dependencies { -> implementation("kscience.kmath:kmath-ast:0.1.4-dev-8") +> implementation("scientifik:kmath-ast:0.1.4-dev-8") > } > ``` > @@ -52,12 +52,12 @@ RealField.mstInField { symbol("x") + 2 }.compile() … leads to generation of bytecode, which can be decompiled to the following Java class: ```java -package kscience.kmath.asm.generated; +package scientifik.kmath.asm.generated; import java.util.Map; -import kscience.kmath.asm.internal.MapIntrinsics; -import kscience.kmath.expressions.Expression; -import kscience.kmath.operations.RealField; +import scientifik.kmath.asm.internal.MapIntrinsics; +import scientifik.kmath.expressions.Expression; +import scientifik.kmath.operations.RealField; public final class AsmCompiledExpression_1073786867_0 implements Expression { private final RealField algebra; diff --git a/kmath-ast/build.gradle.kts b/kmath-ast/build.gradle.kts index 3e3c0475f..d13a7712d 100644 --- a/kmath-ast/build.gradle.kts +++ b/kmath-ast/build.gradle.kts @@ -1,23 +1,23 @@ -plugins { - id("ru.mipt.npm.mpp") -} +plugins { id("scientifik.mpp") } kotlin.sourceSets { +// all { +// languageSettings.apply{ +// enableLanguageFeature("NewInference") +// } +// } commonMain { dependencies { api(project(":kmath-core")) + implementation("com.github.h0tk3y.betterParse:better-parse:0.4.0") } } jvmMain { dependencies { - api("com.github.h0tk3y.betterParse:better-parse:0.4.0") implementation("org.ow2.asm:asm:8.0.1") implementation("org.ow2.asm:asm-commons:8.0.1") + implementation(kotlin("reflect")) } } -} - -readme{ - maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE } \ No newline at end of file diff --git a/kmath-ast/src/commonMain/kotlin/kscience/kmath/ast/MstAlgebra.kt b/kmath-ast/src/commonMain/kotlin/kscience/kmath/ast/MstAlgebra.kt deleted file mode 100644 index 6ee6ab9af..000000000 --- a/kmath-ast/src/commonMain/kotlin/kscience/kmath/ast/MstAlgebra.kt +++ /dev/null @@ -1,120 +0,0 @@ -package kscience.kmath.ast - -import kscience.kmath.operations.* - -/** - * [Algebra] over [MST] nodes. - */ -public object MstAlgebra : NumericAlgebra { - override fun number(value: Number): MST.Numeric = MST.Numeric(value) - - override fun symbol(value: String): MST.Symbolic = MST.Symbolic(value) - - override fun unaryOperation(operation: String, arg: MST): MST.Unary = - MST.Unary(operation, arg) - - override fun binaryOperation(operation: String, left: MST, right: MST): MST.Binary = - MST.Binary(operation, left, right) -} - -/** - * [Space] over [MST] nodes. - */ -public object MstSpace : Space, NumericAlgebra { - override val zero: MST.Numeric by lazy { number(0.0) } - - override fun number(value: Number): MST.Numeric = MstAlgebra.number(value) - override fun symbol(value: String): MST.Symbolic = MstAlgebra.symbol(value) - override fun add(a: MST, b: MST): MST.Binary = binaryOperation(SpaceOperations.PLUS_OPERATION, a, b) - override fun multiply(a: MST, k: Number): MST.Binary = binaryOperation(RingOperations.TIMES_OPERATION, a, number(k)) - - override fun binaryOperation(operation: String, left: MST, right: MST): MST.Binary = - MstAlgebra.binaryOperation(operation, left, right) - - override fun unaryOperation(operation: String, arg: MST): MST.Unary = MstAlgebra.unaryOperation(operation, arg) -} - -/** - * [Ring] over [MST] nodes. - */ -public object MstRing : Ring, NumericAlgebra { - override val zero: MST.Numeric - get() = MstSpace.zero - - override val one: MST.Numeric by lazy { number(1.0) } - - override fun number(value: Number): MST.Numeric = MstSpace.number(value) - override fun symbol(value: String): MST.Symbolic = MstSpace.symbol(value) - override fun add(a: MST, b: MST): MST.Binary = MstSpace.add(a, b) - override fun multiply(a: MST, k: Number): MST.Binary = MstSpace.multiply(a, k) - override fun multiply(a: MST, b: MST): MST.Binary = binaryOperation(RingOperations.TIMES_OPERATION, a, b) - - override fun binaryOperation(operation: String, left: MST, right: MST): MST.Binary = - MstSpace.binaryOperation(operation, left, right) - - override fun unaryOperation(operation: String, arg: MST): MST.Unary = MstSpace.unaryOperation(operation, arg) -} - -/** - * [Field] over [MST] nodes. - */ -public object MstField : Field { - public override val zero: MST.Numeric - get() = MstRing.zero - - public override val one: MST.Numeric - get() = MstRing.one - - public override fun symbol(value: String): MST.Symbolic = MstRing.symbol(value) - public override fun number(value: Number): MST.Numeric = MstRing.number(value) - public override fun add(a: MST, b: MST): MST.Binary = MstRing.add(a, b) - public override fun multiply(a: MST, k: Number): MST.Binary = MstRing.multiply(a, k) - public override fun multiply(a: MST, b: MST): MST.Binary = MstRing.multiply(a, b) - public override fun divide(a: MST, b: MST): MST.Binary = binaryOperation(FieldOperations.DIV_OPERATION, a, b) - - public override fun binaryOperation(operation: String, left: MST, right: MST): MST.Binary = - MstRing.binaryOperation(operation, left, right) - - override fun unaryOperation(operation: String, arg: MST): MST.Unary = MstRing.unaryOperation(operation, arg) -} - -/** - * [ExtendedField] over [MST] nodes. - */ -public object MstExtendedField : ExtendedField { - override val zero: MST.Numeric - get() = MstField.zero - - override val one: MST.Numeric - get() = MstField.one - - override fun symbol(value: String): MST.Symbolic = MstField.symbol(value) - override fun number(value: Number): MST.Numeric = MstField.number(value) - override fun sin(arg: MST): MST.Unary = unaryOperation(TrigonometricOperations.SIN_OPERATION, arg) - override fun cos(arg: MST): MST.Unary = unaryOperation(TrigonometricOperations.COS_OPERATION, arg) - override fun tan(arg: MST): MST.Unary = unaryOperation(TrigonometricOperations.TAN_OPERATION, arg) - override fun asin(arg: MST): MST.Unary = unaryOperation(TrigonometricOperations.ASIN_OPERATION, arg) - override fun acos(arg: MST): MST.Unary = unaryOperation(TrigonometricOperations.ACOS_OPERATION, arg) - override fun atan(arg: MST): MST.Unary = unaryOperation(TrigonometricOperations.ATAN_OPERATION, arg) - override fun sinh(arg: MST): MST.Unary = unaryOperation(HyperbolicOperations.SINH_OPERATION, arg) - override fun cosh(arg: MST): MST.Unary = unaryOperation(HyperbolicOperations.COSH_OPERATION, arg) - override fun tanh(arg: MST): MST.Unary = unaryOperation(HyperbolicOperations.TANH_OPERATION, arg) - override fun asinh(arg: MST): MST.Unary = unaryOperation(HyperbolicOperations.ASINH_OPERATION, arg) - override fun acosh(arg: MST): MST.Unary = unaryOperation(HyperbolicOperations.ACOSH_OPERATION, arg) - override fun atanh(arg: MST): MST.Unary = unaryOperation(HyperbolicOperations.ATANH_OPERATION, arg) - override fun add(a: MST, b: MST): MST.Binary = MstField.add(a, b) - override fun multiply(a: MST, k: Number): MST.Binary = MstField.multiply(a, k) - override fun multiply(a: MST, b: MST): MST.Binary = MstField.multiply(a, b) - override fun divide(a: MST, b: MST): MST.Binary = MstField.divide(a, b) - - override fun power(arg: MST, pow: Number): MST.Binary = - binaryOperation(PowerOperations.POW_OPERATION, arg, number(pow)) - - override fun exp(arg: MST): MST.Unary = unaryOperation(ExponentialOperations.EXP_OPERATION, arg) - override fun ln(arg: MST): MST.Unary = unaryOperation(ExponentialOperations.LN_OPERATION, arg) - - override fun binaryOperation(operation: String, left: MST, right: MST): MST.Binary = - MstField.binaryOperation(operation, left, right) - - override fun unaryOperation(operation: String, arg: MST): MST.Unary = MstField.unaryOperation(operation, arg) -} diff --git a/kmath-ast/src/commonMain/kotlin/kscience/kmath/ast/MstExpression.kt b/kmath-ast/src/commonMain/kotlin/kscience/kmath/ast/MstExpression.kt deleted file mode 100644 index f68e3f5f8..000000000 --- a/kmath-ast/src/commonMain/kotlin/kscience/kmath/ast/MstExpression.kt +++ /dev/null @@ -1,124 +0,0 @@ -package kscience.kmath.ast - -import kscience.kmath.expressions.* -import kscience.kmath.operations.* -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -/** - * The expression evaluates MST on-flight. Should be much faster than functional expression, but slower than - * ASM-generated expressions. - * - * @property algebra the algebra that provides operations. - * @property mst the [MST] node. - * @author Alexander Nozik - */ -public class MstExpression>(public val algebra: A, public val mst: MST) : Expression { - private inner class InnerAlgebra(val arguments: Map) : NumericAlgebra { - override fun symbol(value: String): T = arguments[StringSymbol(value)] ?: algebra.symbol(value) - override fun unaryOperation(operation: String, arg: T): T = algebra.unaryOperation(operation, arg) - - override fun binaryOperation(operation: String, left: T, right: T): T = - algebra.binaryOperation(operation, left, right) - - @Suppress("UNCHECKED_CAST") - override fun number(value: Number): T = if (algebra is NumericAlgebra<*>) - (algebra as NumericAlgebra).number(value) - else - error("Numeric nodes are not supported by $this") - } - - override operator fun invoke(arguments: Map): T = InnerAlgebra(arguments).evaluate(mst) -} - -/** - * Builds [MstExpression] over [Algebra]. - * - * @author Alexander Nozik - */ -public inline fun , E : Algebra> A.mst( - mstAlgebra: E, - block: E.() -> MST, -): MstExpression = MstExpression(this, mstAlgebra.block()) - -/** - * Builds [MstExpression] over [Space]. - * - * @author Alexander Nozik - */ -public inline fun > A.mstInSpace(block: MstSpace.() -> MST): MstExpression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return MstExpression(this, MstSpace.block()) -} - -/** - * Builds [MstExpression] over [Ring]. - * - * @author Alexander Nozik - */ -public inline fun > A.mstInRing(block: MstRing.() -> MST): MstExpression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return MstExpression(this, MstRing.block()) -} - -/** - * Builds [MstExpression] over [Field]. - * - * @author Alexander Nozik - */ -public inline fun > A.mstInField(block: MstField.() -> MST): MstExpression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return MstExpression(this, MstField.block()) -} - -/** - * Builds [MstExpression] over [ExtendedField]. - * - * @author Iaroslav Postovalov - */ -public inline fun > A.mstInExtendedField(block: MstExtendedField.() -> MST): MstExpression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return MstExpression(this, MstExtendedField.block()) -} - -/** - * Builds [MstExpression] over [FunctionalExpressionSpace]. - * - * @author Alexander Nozik - */ -public inline fun > FunctionalExpressionSpace.mstInSpace(block: MstSpace.() -> MST): MstExpression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return algebra.mstInSpace(block) -} - -/** - * Builds [MstExpression] over [FunctionalExpressionRing]. - * - * @author Alexander Nozik - */ -public inline fun > FunctionalExpressionRing.mstInRing(block: MstRing.() -> MST): MstExpression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return algebra.mstInRing(block) -} - -/** - * Builds [MstExpression] over [FunctionalExpressionField]. - * - * @author Alexander Nozik - */ -public inline fun > FunctionalExpressionField.mstInField(block: MstField.() -> MST): MstExpression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return algebra.mstInField(block) -} - -/** - * Builds [MstExpression] over [FunctionalExpressionExtendedField]. - * - * @author Iaroslav Postovalov - */ -public inline fun > FunctionalExpressionExtendedField.mstInExtendedField( - block: MstExtendedField.() -> MST, -): MstExpression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return algebra.mstInExtendedField(block) -} diff --git a/kmath-ast/src/commonMain/kotlin/kscience/kmath/ast/MST.kt b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MST.kt similarity index 76% rename from kmath-ast/src/commonMain/kotlin/kscience/kmath/ast/MST.kt rename to kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MST.kt index f312323b9..0e8151c04 100644 --- a/kmath-ast/src/commonMain/kotlin/kscience/kmath/ast/MST.kt +++ b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MST.kt @@ -1,28 +1,26 @@ -package kscience.kmath.ast +package scientifik.kmath.ast -import kscience.kmath.operations.Algebra -import kscience.kmath.operations.NumericAlgebra -import kscience.kmath.operations.RealField +import scientifik.kmath.operations.Algebra +import scientifik.kmath.operations.NumericAlgebra +import scientifik.kmath.operations.RealField /** * A Mathematical Syntax Tree node for mathematical expressions. - * - * @author Alexander Nozik */ -public sealed class MST { +sealed class MST { /** * A node containing raw string. * * @property value the value of this node. */ - public data class Symbolic(val value: String) : MST() + data class Symbolic(val value: String) : MST() /** * A node containing a numeric value or scalar. * * @property value the value of this number. */ - public data class Numeric(val value: Number) : MST() + data class Numeric(val value: Number) : MST() /** * A node containing an unary operation. @@ -30,7 +28,9 @@ public sealed class MST { * @property operation the identifier of operation. * @property value the argument of this operation. */ - public data class Unary(val operation: String, val value: MST) : MST() + data class Unary(val operation: String, val value: MST) : MST() { + companion object + } /** * A node containing binary operation. @@ -39,7 +39,9 @@ public sealed class MST { * @property left the left operand. * @property right the right operand. */ - public data class Binary(val operation: String, val left: MST, val right: MST) : MST() + data class Binary(val operation: String, val left: MST, val right: MST) : MST() { + companion object + } } // TODO add a function with named arguments @@ -50,9 +52,8 @@ public sealed class MST { * @receiver the algebra that provides operations. * @param node the node to evaluate. * @return the value of expression. - * @author Alexander Nozik */ -public fun Algebra.evaluate(node: MST): T = when (node) { +fun Algebra.evaluate(node: MST): T = when (node) { is MST.Numeric -> (this as? NumericAlgebra)?.number(node.value) ?: error("Numeric nodes are not supported by $this") is MST.Symbolic -> symbol(node.value) @@ -83,4 +84,4 @@ public fun Algebra.evaluate(node: MST): T = when (node) { * @param algebra the algebra that provides operations. * @return the value of expression. */ -public fun MST.interpret(algebra: Algebra): T = algebra.evaluate(this) +fun MST.interpret(algebra: Algebra): T = algebra.evaluate(this) diff --git a/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MstAlgebra.kt b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MstAlgebra.kt new file mode 100644 index 000000000..23deae24b --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MstAlgebra.kt @@ -0,0 +1,102 @@ +package scientifik.kmath.ast + +import scientifik.kmath.operations.* + +/** + * [Algebra] over [MST] nodes. + */ +object MstAlgebra : NumericAlgebra { + override fun number(value: Number): MST = MST.Numeric(value) + + override fun symbol(value: String): MST = MST.Symbolic(value) + + override fun unaryOperation(operation: String, arg: MST): MST = + MST.Unary(operation, arg) + + override fun binaryOperation(operation: String, left: MST, right: MST): MST = + MST.Binary(operation, left, right) +} + +/** + * [Space] over [MST] nodes. + */ +object MstSpace : Space, NumericAlgebra { + override val zero: MST = number(0.0) + + override fun number(value: Number): MST = MstAlgebra.number(value) + override fun symbol(value: String): MST = MstAlgebra.symbol(value) + override fun add(a: MST, b: MST): MST = binaryOperation(SpaceOperations.PLUS_OPERATION, a, b) + override fun multiply(a: MST, k: Number): MST = binaryOperation(RingOperations.TIMES_OPERATION, a, number(k)) + + override fun binaryOperation(operation: String, left: MST, right: MST): MST = + MstAlgebra.binaryOperation(operation, left, right) + + override fun unaryOperation(operation: String, arg: MST): MST = MstAlgebra.unaryOperation(operation, arg) +} + +/** + * [Ring] over [MST] nodes. + */ +object MstRing : Ring, NumericAlgebra { + override val zero: MST = number(0.0) + override val one: MST = number(1.0) + + override fun number(value: Number): MST = MstSpace.number(value) + override fun symbol(value: String): MST = MstSpace.symbol(value) + override fun add(a: MST, b: MST): MST = MstSpace.add(a, b) + + override fun multiply(a: MST, k: Number): MST = MstSpace.multiply(a, k) + + override fun multiply(a: MST, b: MST): MST = binaryOperation(RingOperations.TIMES_OPERATION, a, b) + + override fun binaryOperation(operation: String, left: MST, right: MST): MST = + MstSpace.binaryOperation(operation, left, right) + + override fun unaryOperation(operation: String, arg: MST): MST = MstAlgebra.unaryOperation(operation, arg) +} + +/** + * [Field] over [MST] nodes. + */ +object MstField : Field { + override val zero: MST = number(0.0) + override val one: MST = number(1.0) + + override fun symbol(value: String): MST = MstRing.symbol(value) + override fun number(value: Number): MST = MstRing.number(value) + override fun add(a: MST, b: MST): MST = MstRing.add(a, b) + override fun multiply(a: MST, k: Number): MST = MstRing.multiply(a, k) + override fun multiply(a: MST, b: MST): MST = MstRing.multiply(a, b) + override fun divide(a: MST, b: MST): MST = binaryOperation(FieldOperations.DIV_OPERATION, a, b) + + override fun binaryOperation(operation: String, left: MST, right: MST): MST = + MstRing.binaryOperation(operation, left, right) + + override fun unaryOperation(operation: String, arg: MST): MST = MstRing.unaryOperation(operation, arg) +} + +/** + * [ExtendedField] over [MST] nodes. + */ +object MstExtendedField : ExtendedField { + override val zero: MST = number(0.0) + override val one: MST = number(1.0) + + override fun sin(arg: MST): MST = unaryOperation(TrigonometricOperations.SIN_OPERATION, arg) + override fun cos(arg: MST): MST = unaryOperation(TrigonometricOperations.COS_OPERATION, arg) + override fun asin(arg: MST): MST = unaryOperation(TrigonometricOperations.ASIN_OPERATION, arg) + override fun acos(arg: MST): MST = unaryOperation(TrigonometricOperations.ACOS_OPERATION, arg) + override fun atan(arg: MST): MST = unaryOperation(TrigonometricOperations.ATAN_OPERATION, arg) + override fun add(a: MST, b: MST): MST = MstField.add(a, b) + override fun multiply(a: MST, k: Number): MST = MstField.multiply(a, k) + override fun multiply(a: MST, b: MST): MST = MstField.multiply(a, b) + override fun divide(a: MST, b: MST): MST = MstField.divide(a, b) + override fun power(arg: MST, pow: Number): MST = binaryOperation(PowerOperations.POW_OPERATION, arg, number(pow)) + override fun exp(arg: MST): MST = unaryOperation(ExponentialOperations.EXP_OPERATION, arg) + override fun ln(arg: MST): MST = unaryOperation(ExponentialOperations.LN_OPERATION, arg) + + override fun binaryOperation(operation: String, left: MST, right: MST): MST = + MstField.binaryOperation(operation, left, right) + + override fun unaryOperation(operation: String, arg: MST): MST = MstField.unaryOperation(operation, arg) +} diff --git a/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MstExpression.kt b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MstExpression.kt new file mode 100644 index 000000000..59f3f15d8 --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MstExpression.kt @@ -0,0 +1,88 @@ +package scientifik.kmath.ast + +import scientifik.kmath.expressions.* +import scientifik.kmath.operations.* + +/** + * The expression evaluates MST on-flight. Should be much faster than functional expression, but slower than + * ASM-generated expressions. + * + * @property algebra the algebra that provides operations. + * @property mst the [MST] node. + */ +class MstExpression(val algebra: Algebra, val mst: MST) : Expression { + private inner class InnerAlgebra(val arguments: Map) : NumericAlgebra { + override fun symbol(value: String): T = arguments[value] ?: algebra.symbol(value) + override fun unaryOperation(operation: String, arg: T): T = algebra.unaryOperation(operation, arg) + + override fun binaryOperation(operation: String, left: T, right: T): T = + algebra.binaryOperation(operation, left, right) + + override fun number(value: Number): T = if (algebra is NumericAlgebra) + algebra.number(value) + else + error("Numeric nodes are not supported by $this") + } + + override fun invoke(arguments: Map): T = InnerAlgebra(arguments).evaluate(mst) +} + +/** + * Builds [MstExpression] over [Algebra]. + */ +inline fun , E : Algebra> A.mst( + mstAlgebra: E, + block: E.() -> MST +): MstExpression = MstExpression(this, mstAlgebra.block()) + +/** + * Builds [MstExpression] over [Space]. + */ +inline fun Space.mstInSpace(block: MstSpace.() -> MST): MstExpression = + MstExpression(this, MstSpace.block()) + +/** + * Builds [MstExpression] over [Ring]. + */ +inline fun Ring.mstInRing(block: MstRing.() -> MST): MstExpression = + MstExpression(this, MstRing.block()) + +/** + * Builds [MstExpression] over [Field]. + */ +inline fun Field.mstInField(block: MstField.() -> MST): MstExpression = + MstExpression(this, MstField.block()) + +/** + * Builds [MstExpression] over [ExtendedField]. + */ +inline fun Field.mstInExtendedField(block: MstExtendedField.() -> MST): MstExpression = + MstExpression(this, MstExtendedField.block()) + +/** + * Builds [MstExpression] over [FunctionalExpressionSpace]. + */ +inline fun > FunctionalExpressionSpace.mstInSpace( + block: MstSpace.() -> MST +): MstExpression = algebra.mstInSpace(block) + +/** + * Builds [MstExpression] over [FunctionalExpressionRing]. + */ +inline fun > FunctionalExpressionRing.mstInRing( + block: MstRing.() -> MST +): MstExpression = algebra.mstInRing(block) + +/** + * Builds [MstExpression] over [FunctionalExpressionField]. + */ +inline fun > FunctionalExpressionField.mstInField( + block: MstField.() -> MST +): MstExpression = algebra.mstInField(block) + +/** + * Builds [MstExpression] over [FunctionalExpressionExtendedField]. + */ +inline fun > FunctionalExpressionExtendedField.mstInExtendedField( + block: MstExtendedField.() -> MST +): MstExpression = algebra.mstInExtendedField(block) diff --git a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/ast/parser.kt b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/parser.kt similarity index 59% rename from kmath-ast/src/jvmMain/kotlin/kscience/kmath/ast/parser.kt rename to kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/parser.kt index 79cf77a6a..cba335a8d 100644 --- a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/ast/parser.kt +++ b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/parser.kt @@ -1,4 +1,4 @@ -package kscience.kmath.ast +package scientifik.kmath.ast import com.github.h0tk3y.betterParse.combinators.* import com.github.h0tk3y.betterParse.grammar.Grammar @@ -7,53 +7,51 @@ import com.github.h0tk3y.betterParse.grammar.parser import com.github.h0tk3y.betterParse.grammar.tryParseToEnd import com.github.h0tk3y.betterParse.lexer.Token import com.github.h0tk3y.betterParse.lexer.TokenMatch -import com.github.h0tk3y.betterParse.lexer.literalToken import com.github.h0tk3y.betterParse.lexer.regexToken import com.github.h0tk3y.betterParse.parser.ParseResult import com.github.h0tk3y.betterParse.parser.Parser -import kscience.kmath.operations.FieldOperations -import kscience.kmath.operations.PowerOperations -import kscience.kmath.operations.RingOperations -import kscience.kmath.operations.SpaceOperations +import scientifik.kmath.operations.FieldOperations +import scientifik.kmath.operations.PowerOperations +import scientifik.kmath.operations.RingOperations +import scientifik.kmath.operations.SpaceOperations /** - * TODO move to common after IR version is released - * @author Alexander Nozik and Iaroslav Postovalov + * TODO move to core */ -public object ArithmeticsEvaluator : Grammar() { +object ArithmeticsEvaluator : Grammar() { // TODO replace with "...".toRegex() when better-parse 0.4.1 is released private val num: Token by regexToken("[\\d.]+(?:[eE][-+]?\\d+)?") private val id: Token by regexToken("[a-z_A-Z][\\da-z_A-Z]*") - private val lpar: Token by literalToken("(") - private val rpar: Token by literalToken(")") - private val comma: Token by literalToken(",") - private val mul: Token by literalToken("*") - private val pow: Token by literalToken("^") - private val div: Token by literalToken("/") - private val minus: Token by literalToken("-") - private val plus: Token by literalToken("+") + private val lpar: Token by regexToken("\\(") + private val rpar: Token by regexToken("\\)") + private val comma: Token by regexToken(",") + private val mul: Token by regexToken("\\*") + private val pow: Token by regexToken("\\^") + private val div: Token by regexToken("/") + private val minus: Token by regexToken("-") + private val plus: Token by regexToken("\\+") private val ws: Token by regexToken("\\s+", ignore = true) private val number: Parser by num use { MST.Numeric(text.toDouble()) } private val singular: Parser by id use { MST.Symbolic(text) } - private val unaryFunction: Parser by (id and -lpar and parser(ArithmeticsEvaluator::subSumChain) and -rpar) + private val unaryFunction: Parser by (id and skip(lpar) and parser(::subSumChain) and skip(rpar)) .map { (id, term) -> MST.Unary(id.text, term) } private val binaryFunction: Parser by id - .and(-lpar) - .and(parser(ArithmeticsEvaluator::subSumChain)) - .and(-comma) - .and(parser(ArithmeticsEvaluator::subSumChain)) - .and(-rpar) + .and(skip(lpar)) + .and(parser(::subSumChain)) + .and(skip(comma)) + .and(parser(::subSumChain)) + .and(skip(rpar)) .map { (id, left, right) -> MST.Binary(id.text, left, right) } private val term: Parser by number .or(binaryFunction) .or(unaryFunction) .or(singular) - .or(-minus and parser(ArithmeticsEvaluator::term) map { MST.Unary(SpaceOperations.MINUS_OPERATION, it) }) - .or(-lpar and parser(ArithmeticsEvaluator::subSumChain) and -rpar) + .or(skip(minus) and parser(::term) map { MST.Unary(SpaceOperations.MINUS_OPERATION, it) }) + .or(skip(lpar) and parser(::subSumChain) and skip(rpar)) private val powChain: Parser by leftAssociative(term = term, operator = pow) { a, _, b -> MST.Binary(PowerOperations.POW_OPERATION, a, b) @@ -83,12 +81,12 @@ public object ArithmeticsEvaluator : Grammar() { } /** - * Tries to parse the string into [MST]. Returns [ParseResult] representing expression or error. + * Tries to parse the string into [MST]. * * @receiver the string to parse. * @return the [MST] node. */ -public fun String.tryParseMath(): ParseResult = ArithmeticsEvaluator.tryParseToEnd(this) +fun String.tryParseMath(): ParseResult = ArithmeticsEvaluator.tryParseToEnd(this) /** * Parses the string into [MST]. @@ -96,4 +94,4 @@ public fun String.tryParseMath(): ParseResult = ArithmeticsEvaluator.tryPar * @receiver the string to parse. * @return the [MST] node. */ -public fun String.parseMath(): MST = ArithmeticsEvaluator.parseToEnd(this) +fun String.parseMath(): MST = ArithmeticsEvaluator.parseToEnd(this) diff --git a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/asm.kt b/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/asm.kt deleted file mode 100644 index 9ccfa464c..000000000 --- a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/asm.kt +++ /dev/null @@ -1,73 +0,0 @@ -package kscience.kmath.asm - -import kscience.kmath.asm.internal.AsmBuilder -import kscience.kmath.asm.internal.MstType -import kscience.kmath.asm.internal.buildAlgebraOperationCall -import kscience.kmath.asm.internal.buildName -import kscience.kmath.ast.MST -import kscience.kmath.ast.MstExpression -import kscience.kmath.expressions.Expression -import kscience.kmath.operations.Algebra - -/** - * Compiles given MST to an Expression using AST compiler. - * - * @param type the target type. - * @param algebra the target algebra. - * @return the compiled expression. - * @author Alexander Nozik - */ -@PublishedApi -internal fun MST.compileWith(type: Class, algebra: Algebra): Expression { - fun AsmBuilder.visit(node: MST): Unit = when (node) { - is MST.Symbolic -> { - val symbol = try { - algebra.symbol(node.value) - } catch (ignored: Throwable) { - null - } - - if (symbol != null) - loadTConstant(symbol) - else - loadVariable(node.value) - } - - is MST.Numeric -> loadNumeric(node.value) - - is MST.Unary -> buildAlgebraOperationCall( - context = algebra, - name = node.operation, - fallbackMethodName = "unaryOperation", - parameterTypes = arrayOf(MstType.fromMst(node.value)) - ) { visit(node.value) } - - is MST.Binary -> buildAlgebraOperationCall( - context = algebra, - name = node.operation, - fallbackMethodName = "binaryOperation", - parameterTypes = arrayOf(MstType.fromMst(node.left), MstType.fromMst(node.right)) - ) { - visit(node.left) - visit(node.right) - } - } - - return AsmBuilder(type, algebra, buildName(this)) { visit(this@compileWith) }.getInstance() -} - -/** - * Compiles an [MST] to ASM using given algebra. - * - * @author Alexander Nozik. - */ -public inline fun Algebra.expression(mst: MST): Expression = - mst.compileWith(T::class.java, this) - -/** - * Optimizes performance of an [MstExpression] using ASM codegen. - * - * @author Alexander Nozik. - */ -public inline fun MstExpression>.compile(): Expression = - mst.compileWith(T::class.java, algebra) diff --git a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/mapIntrinsics.kt b/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/mapIntrinsics.kt deleted file mode 100644 index 588b9611a..000000000 --- a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/mapIntrinsics.kt +++ /dev/null @@ -1,13 +0,0 @@ -@file:JvmName("MapIntrinsics") - -package kscience.kmath.asm.internal - -import kscience.kmath.expressions.StringSymbol -import kscience.kmath.expressions.Symbol - -/** - * Gets value with given [key] or throws [NoSuchElementException] whenever it is not present. - * - * @author Iaroslav Postovalov - */ -internal fun Map.getOrFail(key: String): V = getValue(StringSymbol(key)) diff --git a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt new file mode 100644 index 000000000..ee0ea15ff --- /dev/null +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt @@ -0,0 +1,64 @@ +package scientifik.kmath.asm + +import scientifik.kmath.asm.internal.AsmBuilder +import scientifik.kmath.asm.internal.MstType +import scientifik.kmath.asm.internal.buildAlgebraOperationCall +import scientifik.kmath.asm.internal.buildName +import scientifik.kmath.ast.MST +import scientifik.kmath.ast.MstExpression +import scientifik.kmath.expressions.Expression +import scientifik.kmath.operations.Algebra +import kotlin.reflect.KClass + +/** + * Compile given MST to an Expression using AST compiler + */ +fun MST.compileWith(type: KClass, algebra: Algebra): Expression { + fun AsmBuilder.visit(node: MST) { + when (node) { + is MST.Symbolic -> { + val symbol = try { + algebra.symbol(node.value) + } catch (ignored: Throwable) { + null + } + + if (symbol != null) + loadTConstant(symbol) + else + loadVariable(node.value) + } + + is MST.Numeric -> loadNumeric(node.value) + + is MST.Unary -> buildAlgebraOperationCall( + context = algebra, + name = node.operation, + fallbackMethodName = "unaryOperation", + parameterTypes = arrayOf(MstType.fromMst(node.value)) + ) { visit(node.value) } + + is MST.Binary -> buildAlgebraOperationCall( + context = algebra, + name = node.operation, + fallbackMethodName = "binaryOperation", + parameterTypes = arrayOf(MstType.fromMst(node.left), MstType.fromMst(node.right)) + ) { + visit(node.left) + visit(node.right) + } + } + } + + return AsmBuilder(type, algebra, buildName(this)) { visit(this@compileWith) }.getInstance() +} + +/** + * Compile an [MST] to ASM using given algebra + */ +inline fun Algebra.expression(mst: MST): Expression = mst.compileWith(T::class, this) + +/** + * Optimize performance of an [MstExpression] using ASM codegen + */ +inline fun MstExpression.compile(): Expression = mst.compileWith(T::class, algebra) diff --git a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/AsmBuilder.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/AsmBuilder.kt similarity index 85% rename from kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/AsmBuilder.kt rename to kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/AsmBuilder.kt index a1e482103..f8c159baf 100644 --- a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/AsmBuilder.kt +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/AsmBuilder.kt @@ -1,15 +1,16 @@ -package kscience.kmath.asm.internal +package scientifik.kmath.asm.internal -import kscience.kmath.asm.internal.AsmBuilder.ClassLoader -import kscience.kmath.ast.MST -import kscience.kmath.expressions.Expression -import kscience.kmath.operations.Algebra -import kscience.kmath.operations.NumericAlgebra import org.objectweb.asm.* import org.objectweb.asm.Opcodes.* import org.objectweb.asm.commons.InstructionAdapter +import scientifik.kmath.asm.internal.AsmBuilder.ClassLoader +import scientifik.kmath.ast.MST +import scientifik.kmath.expressions.Expression +import scientifik.kmath.operations.Algebra +import scientifik.kmath.operations.NumericAlgebra import java.util.* import java.util.stream.Collectors +import kotlin.reflect.KClass /** * ASM Builder is a structure that abstracts building a class designated to unwrap [MST] to plain Java expression. @@ -19,19 +20,18 @@ import java.util.stream.Collectors * @property algebra the algebra the applied AsmExpressions use. * @property className the unique class name of new loaded class. * @property invokeLabel0Visitor the function to apply to this object when generating invoke method, label 0. - * @author Iaroslav Postovalov */ internal class AsmBuilder internal constructor( - private val classOfT: Class<*>, + private val classOfT: KClass<*>, private val algebra: Algebra, private val className: String, - private val invokeLabel0Visitor: AsmBuilder.() -> Unit, + private val invokeLabel0Visitor: AsmBuilder.() -> Unit ) { /** * Internal classloader of [AsmBuilder] with alias to define class from byte array. */ private class ClassLoader(parent: java.lang.ClassLoader) : java.lang.ClassLoader(parent) { - fun defineClass(name: String?, b: ByteArray): Class<*> = defineClass(name, b, 0, b.size) + internal fun defineClass(name: String?, b: ByteArray): Class<*> = defineClass(name, b, 0, b.size) } /** @@ -42,7 +42,7 @@ internal class AsmBuilder internal constructor( /** * ASM Type for [algebra]. */ - private val tAlgebraType: Type = algebra.javaClass.asm + private val tAlgebraType: Type = algebra::class.asm /** * ASM type for [T]. @@ -54,6 +54,16 @@ internal class AsmBuilder internal constructor( */ private val classType: Type = Type.getObjectType(className.replace(oldChar = '.', newChar = '/'))!! + /** + * Index of `this` variable in invoke method of the built subclass. + */ + private val invokeThisVar: Int = 0 + + /** + * Index of `arguments` variable in invoke method of the built subclass. + */ + private val invokeArgumentsVar: Int = 1 + /** * List of constants to provide to the subclass. */ @@ -65,22 +75,22 @@ internal class AsmBuilder internal constructor( private lateinit var invokeMethodVisitor: InstructionAdapter /** - * States whether this [AsmBuilder] needs to generate constants field. + * State if this [AsmBuilder] needs to generate constants field. */ private var hasConstants: Boolean = true /** - * States whether [T] a primitive type, so [AsmBuilder] may generate direct primitive calls. + * State if [T] a primitive type, so [AsmBuilder] may generate direct primitive calls. */ internal var primitiveMode: Boolean = false /** - * Primitive type to apply for specific primitive calls. Use [OBJECT_TYPE], if not in [primitiveMode]. + * Primitive type to apple for specific primitive calls. Use [OBJECT_TYPE], if not in [primitiveMode]. */ internal var primitiveMask: Type = OBJECT_TYPE /** - * Boxed primitive type to apply for specific primitive calls. Use [OBJECT_TYPE], if not in [primitiveMode]. + * Boxed primitive type to apple for specific primitive calls. Use [OBJECT_TYPE], if not in [primitiveMode]. */ internal var primitiveMaskBoxed: Type = OBJECT_TYPE @@ -92,7 +102,7 @@ internal class AsmBuilder internal constructor( /** * Stack of useful objects types on stack expected by algebra calls. */ - internal val expectationStack: ArrayDeque = ArrayDeque(1).also { it.push(tType) } + internal val expectationStack: ArrayDeque = ArrayDeque(listOf(tType)) /** * The cache for instance built by this builder. @@ -350,7 +360,7 @@ internal class AsmBuilder internal constructor( * from it). */ private fun loadNumberConstant(value: Number, mustBeBoxed: Boolean) { - val boxed = value.javaClass.asm + val boxed = value::class.asm val primitive = BOXED_TO_PRIMITIVES[boxed] if (primitive != null) { @@ -379,14 +389,22 @@ internal class AsmBuilder internal constructor( * Loads a variable [name] from arguments [Map] parameter of [Expression.invoke]. The [defaultValue] may be * provided. */ - internal fun loadVariable(name: String): Unit = invokeMethodVisitor.run { + internal fun loadVariable(name: String, defaultValue: T? = null): Unit = invokeMethodVisitor.run { load(invokeArgumentsVar, MAP_TYPE) aconst(name) + if (defaultValue != null) + loadTConstant(defaultValue) + invokestatic( MAP_INTRINSICS_TYPE.internalName, "getOrFail", - Type.getMethodDescriptor(OBJECT_TYPE, MAP_TYPE, STRING_TYPE), + + Type.getMethodDescriptor( + OBJECT_TYPE, + MAP_TYPE, + OBJECT_TYPE, + *OBJECT_TYPE.wrapToArrayIf { defaultValue != null }), false ) @@ -421,7 +439,7 @@ internal class AsmBuilder internal constructor( method: String, descriptor: String, expectedArity: Int, - opcode: Int = INVOKEINTERFACE, + opcode: Int = INVOKEINTERFACE ) { run loop@{ repeat(expectedArity) { @@ -456,27 +474,17 @@ internal class AsmBuilder internal constructor( internal fun loadStringConstant(string: String): Unit = invokeMethodVisitor.aconst(string) internal companion object { - /** - * Index of `this` variable in invoke method of the built subclass. - */ - private const val invokeThisVar: Int = 0 - - /** - * Index of `arguments` variable in invoke method of the built subclass. - */ - private const val invokeArgumentsVar: Int = 1 - /** * Maps JVM primitive numbers boxed types to their primitive ASM types. */ - private val SIGNATURE_LETTERS: Map, Type> by lazy { + private val SIGNATURE_LETTERS: Map, Type> by lazy { hashMapOf( - java.lang.Byte::class.java to Type.BYTE_TYPE, - java.lang.Short::class.java to Type.SHORT_TYPE, - java.lang.Integer::class.java to Type.INT_TYPE, - java.lang.Long::class.java to Type.LONG_TYPE, - java.lang.Float::class.java to Type.FLOAT_TYPE, - java.lang.Double::class.java to Type.DOUBLE_TYPE + java.lang.Byte::class to Type.BYTE_TYPE, + java.lang.Short::class to Type.SHORT_TYPE, + java.lang.Integer::class to Type.INT_TYPE, + java.lang.Long::class to Type.LONG_TYPE, + java.lang.Float::class to Type.FLOAT_TYPE, + java.lang.Double::class to Type.DOUBLE_TYPE ) } @@ -514,47 +522,47 @@ internal class AsmBuilder internal constructor( /** * Provides boxed number types values of which can be stored in JVM bytecode constant pool. */ - private val INLINABLE_NUMBERS: Set> by lazy { SIGNATURE_LETTERS.keys } + private val INLINABLE_NUMBERS: Set> by lazy { SIGNATURE_LETTERS.keys } /** * ASM type for [Expression]. */ - internal val EXPRESSION_TYPE: Type by lazy { Type.getObjectType("kscience/kmath/expressions/Expression") } + internal val EXPRESSION_TYPE: Type by lazy { Expression::class.asm } /** * ASM type for [java.lang.Number]. */ - internal val NUMBER_TYPE: Type by lazy { Type.getObjectType("java/lang/Number") } + internal val NUMBER_TYPE: Type by lazy { java.lang.Number::class.asm } /** * ASM type for [java.util.Map]. */ - internal val MAP_TYPE: Type by lazy { Type.getObjectType("java/util/Map") } + internal val MAP_TYPE: Type by lazy { java.util.Map::class.asm } /** * ASM type for [java.lang.Object]. */ - internal val OBJECT_TYPE: Type by lazy { Type.getObjectType("java/lang/Object") } + internal val OBJECT_TYPE: Type by lazy { java.lang.Object::class.asm } /** * ASM type for array of [java.lang.Object]. */ @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "RemoveRedundantQualifierName") - internal val OBJECT_ARRAY_TYPE: Type by lazy { Type.getType("[Ljava/lang/Object;") } + internal val OBJECT_ARRAY_TYPE: Type by lazy { Array::class.asm } /** * ASM type for [Algebra]. */ - internal val ALGEBRA_TYPE: Type by lazy { Type.getObjectType("kscience/kmath/operations/Algebra") } + internal val ALGEBRA_TYPE: Type by lazy { Algebra::class.asm } /** * ASM type for [java.lang.String]. */ - internal val STRING_TYPE: Type by lazy { Type.getObjectType("java/lang/String") } + internal val STRING_TYPE: Type by lazy { java.lang.String::class.asm } /** * ASM type for MapIntrinsics. */ - internal val MAP_INTRINSICS_TYPE: Type by lazy { Type.getObjectType("kscience/kmath/asm/internal/MapIntrinsics") } + internal val MAP_INTRINSICS_TYPE: Type by lazy { Type.getObjectType("scientifik/kmath/asm/internal/MapIntrinsics") } } } diff --git a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/MstType.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/MstType.kt similarity index 62% rename from kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/MstType.kt rename to kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/MstType.kt index 526c27305..bf73d304b 100644 --- a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/MstType.kt +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/MstType.kt @@ -1,10 +1,7 @@ -package kscience.kmath.asm.internal +package scientifik.kmath.asm.internal -import kscience.kmath.ast.MST +import scientifik.kmath.ast.MST -/** - * Represents types known in [MST], numbers and general values. - */ internal enum class MstType { GENERAL, NUMBER; diff --git a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/codegenUtils.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/codegenUtils.kt similarity index 67% rename from kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/codegenUtils.kt rename to kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/codegenUtils.kt index ef9751502..a637289b8 100644 --- a/kmath-ast/src/jvmMain/kotlin/kscience/kmath/asm/internal/codegenUtils.kt +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/codegenUtils.kt @@ -1,69 +1,47 @@ -package kscience.kmath.asm.internal +package scientifik.kmath.asm.internal -import kscience.kmath.ast.MST -import kscience.kmath.expressions.Expression -import kscience.kmath.operations.Algebra -import kscience.kmath.operations.FieldOperations -import kscience.kmath.operations.RingOperations -import kscience.kmath.operations.SpaceOperations import org.objectweb.asm.* import org.objectweb.asm.Opcodes.INVOKEVIRTUAL import org.objectweb.asm.commons.InstructionAdapter +import scientifik.kmath.ast.MST +import scientifik.kmath.expressions.Expression +import scientifik.kmath.operations.Algebra import java.lang.reflect.Method -import java.util.* -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract +import kotlin.reflect.KClass private val methodNameAdapters: Map, String> by lazy { hashMapOf( - SpaceOperations.PLUS_OPERATION to 2 to "add", - RingOperations.TIMES_OPERATION to 2 to "multiply", - FieldOperations.DIV_OPERATION to 2 to "divide", - SpaceOperations.PLUS_OPERATION to 1 to "unaryPlus", - SpaceOperations.MINUS_OPERATION to 1 to "unaryMinus", - SpaceOperations.MINUS_OPERATION to 2 to "minus" + "+" to 2 to "add", + "*" to 2 to "multiply", + "/" to 2 to "divide", + "+" to 1 to "unaryPlus", + "-" to 1 to "unaryMinus", + "-" to 2 to "minus" ) } -/** - * Returns ASM [Type] for given [Class]. - * - * @author Iaroslav Postovalov - */ -internal inline val Class<*>.asm: Type - get() = Type.getType(this) +internal val KClass<*>.asm: Type + get() = Type.getType(java) /** * Returns singleton array with this value if the [predicate] is true, returns empty array otherwise. - * - * @author Iaroslav Postovalov */ -internal inline fun T.wrapToArrayIf(predicate: (T) -> Boolean): Array { - contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } - return if (predicate(this)) arrayOf(this) else emptyArray() -} +internal inline fun T.wrapToArrayIf(predicate: (T) -> Boolean): Array = + if (predicate(this)) arrayOf(this) else emptyArray() /** * Creates an [InstructionAdapter] from this [MethodVisitor]. - * - * @author Iaroslav Postovalov */ private fun MethodVisitor.instructionAdapter(): InstructionAdapter = InstructionAdapter(this) /** * Creates an [InstructionAdapter] from this [MethodVisitor] and applies [block] to it. - * - * @author Iaroslav Postovalov */ -internal inline fun MethodVisitor.instructionAdapter(block: InstructionAdapter.() -> Unit): InstructionAdapter { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return instructionAdapter().apply(block) -} +internal fun MethodVisitor.instructionAdapter(block: InstructionAdapter.() -> Unit): InstructionAdapter = + instructionAdapter().apply(block) /** * Constructs a [Label], then applies it to this visitor. - * - * @author Iaroslav Postovalov */ internal fun MethodVisitor.label(): Label = Label().also { visitLabel(it) } @@ -72,11 +50,9 @@ internal fun MethodVisitor.label(): Label = Label().also { visitLabel(it) } * * This methods helps to avoid collisions of class name to prevent loading several classes with the same name. If there * is a colliding class, change [collision] parameter or leave it `0` to check existing classes recursively. - * - * @author Iaroslav Postovalov */ internal tailrec fun buildName(mst: MST, collision: Int = 0): String { - val name = "kscience.kmath.asm.generated.AsmCompiledExpression_${mst.hashCode()}_$collision" + val name = "scientifik.kmath.asm.generated.AsmCompiledExpression_${mst.hashCode()}_$collision" try { Class.forName(name) @@ -88,16 +64,9 @@ internal tailrec fun buildName(mst: MST, collision: Int = 0): String { } @Suppress("FunctionName") -internal inline fun ClassWriter(flags: Int, block: ClassWriter.() -> Unit): ClassWriter { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ClassWriter(flags).apply(block) -} +internal inline fun ClassWriter(flags: Int, block: ClassWriter.() -> Unit): ClassWriter = + ClassWriter(flags).apply(block) -/** - * Invokes [visitField] and applies [block] to the [FieldVisitor]. - * - * @author Iaroslav Postovalov - */ internal inline fun ClassWriter.visitField( access: Int, name: String, @@ -105,10 +74,7 @@ internal inline fun ClassWriter.visitField( signature: String?, value: Any?, block: FieldVisitor.() -> Unit -): FieldVisitor { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return visitField(access, name, descriptor, signature, value).apply(block) -} +): FieldVisitor = visitField(access, name, descriptor, signature, value).apply(block) private fun AsmBuilder.findSpecific(context: Algebra, name: String, parameterTypes: Array): Method? = context.javaClass.methods.find { method -> @@ -127,7 +93,7 @@ private fun AsmBuilder.findSpecific(context: Algebra, name: String, pa * Checks if the target [context] for code generation contains a method with needed [name] and arity, also builds * type expectation stack for needed arity. * - * @author Iaroslav Postovalov + * @return `true` if contains, else `false`. */ private fun AsmBuilder.buildExpectationStack( context: Algebra, @@ -140,7 +106,7 @@ private fun AsmBuilder.buildExpectationStack( if (specific != null) mapTypes(specific, parameterTypes).reversed().forEach { expectationStack.push(it) } else - expectationStack.addAll(Collections.nCopies(arity, tType)) + repeat(arity) { expectationStack.push(tType) } return specific != null } @@ -159,7 +125,7 @@ private fun AsmBuilder.mapTypes(method: Method, parameterTypes: Array AsmBuilder.tryInvokeSpecific( context: Algebra, @@ -169,7 +135,7 @@ private fun AsmBuilder.tryInvokeSpecific( val arity = parameterTypes.size val theName = methodNameAdapters[name to arity] ?: name val spec = findSpecific(context, theName, parameterTypes) ?: return false - val owner = context.javaClass.asm + val owner = context::class.asm invokeAlgebraOperation( owner = owner.internalName, @@ -183,9 +149,7 @@ private fun AsmBuilder.tryInvokeSpecific( } /** - * Builds specialized [context] call with option to fallback to generic algebra operation accepting [String]. - * - * @author Iaroslav Postovalov + * Builds specialized algebra call with option to fallback to generic algebra operation accepting String. */ internal inline fun AsmBuilder.buildAlgebraOperationCall( context: Algebra, @@ -194,7 +158,6 @@ internal inline fun AsmBuilder.buildAlgebraOperationCall( parameterTypes: Array, parameters: AsmBuilder.() -> Unit ) { - contract { callsInPlace(parameters, InvocationKind.EXACTLY_ONCE) } val arity = parameterTypes.size loadAlgebra() if (!buildExpectationStack(context, name, parameterTypes)) loadStringConstant(name) diff --git a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/mapIntrinsics.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/mapIntrinsics.kt new file mode 100644 index 000000000..80e83c1bf --- /dev/null +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/mapIntrinsics.kt @@ -0,0 +1,7 @@ +@file:JvmName("MapIntrinsics") + +package scientifik.kmath.asm.internal + +@JvmOverloads +internal fun Map.getOrFail(key: K, default: V? = null): V = + this[key] ?: default ?: error("Parameter not found: $key") diff --git a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmAlgebras.kt b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmAlgebras.kt similarity index 90% rename from kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmAlgebras.kt rename to kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmAlgebras.kt index 5eebfe43d..3acc6eb28 100644 --- a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmAlgebras.kt +++ b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmAlgebras.kt @@ -1,16 +1,16 @@ -package kscience.kmath.asm +package scietifik.kmath.asm -import kscience.kmath.ast.mstInField -import kscience.kmath.ast.mstInRing -import kscience.kmath.ast.mstInSpace -import kscience.kmath.expressions.invoke -import kscience.kmath.operations.ByteRing -import kscience.kmath.operations.RealField +import scientifik.kmath.asm.compile +import scientifik.kmath.ast.mstInField +import scientifik.kmath.ast.mstInRing +import scientifik.kmath.ast.mstInSpace +import scientifik.kmath.expressions.invoke +import scientifik.kmath.operations.ByteRing +import scientifik.kmath.operations.RealField import kotlin.test.Test import kotlin.test.assertEquals internal class TestAsmAlgebras { - @Test fun space() { val res1 = ByteRing.mstInSpace { diff --git a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmExpressions.kt b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmExpressions.kt similarity index 74% rename from kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmExpressions.kt rename to kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmExpressions.kt index 7f4d453f7..36c254c38 100644 --- a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmExpressions.kt +++ b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmExpressions.kt @@ -1,10 +1,10 @@ -package kscience.kmath.asm +package scietifik.kmath.asm -import kscience.kmath.asm.compile -import kscience.kmath.ast.mstInField -import kscience.kmath.ast.mstInSpace -import kscience.kmath.expressions.invoke -import kscience.kmath.operations.RealField +import scientifik.kmath.asm.compile +import scientifik.kmath.ast.mstInField +import scientifik.kmath.ast.mstInSpace +import scientifik.kmath.expressions.invoke +import scientifik.kmath.operations.RealField import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmSpecialization.kt b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmSpecialization.kt similarity index 87% rename from kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmSpecialization.kt rename to kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmSpecialization.kt index 8f8175acd..a88431e9d 100644 --- a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmSpecialization.kt +++ b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmSpecialization.kt @@ -1,9 +1,9 @@ -package kscience.kmath.asm +package scietifik.kmath.asm -import kscience.kmath.asm.compile -import kscience.kmath.ast.mstInField -import kscience.kmath.expressions.invoke -import kscience.kmath.operations.RealField +import scientifik.kmath.asm.compile +import scientifik.kmath.ast.mstInField +import scientifik.kmath.expressions.invoke +import scientifik.kmath.operations.RealField import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmVariables.kt b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmVariables.kt similarity index 75% rename from kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmVariables.kt rename to kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmVariables.kt index 7b89c74fa..aafc75448 100644 --- a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/asm/TestAsmVariables.kt +++ b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/asm/TestAsmVariables.kt @@ -1,8 +1,8 @@ -package kscience.kmath.asm +package scietifik.kmath.asm -import kscience.kmath.ast.mstInRing -import kscience.kmath.expressions.invoke -import kscience.kmath.operations.ByteRing +import scientifik.kmath.ast.mstInRing +import scientifik.kmath.expressions.invoke +import scientifik.kmath.operations.ByteRing import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith diff --git a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/ast/AsmTest.kt b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/AsmTest.kt similarity index 58% rename from kmath-ast/src/jvmTest/kotlin/kscience/kmath/ast/AsmTest.kt rename to kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/AsmTest.kt index 1ca8aa5dd..75659cc35 100644 --- a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/ast/AsmTest.kt +++ b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/AsmTest.kt @@ -1,12 +1,12 @@ -package kscience.kmath.ast +package scietifik.kmath.ast -import kscience.kmath.asm.compile -import kscience.kmath.asm.expression -import kscience.kmath.ast.mstInField -import kscience.kmath.ast.parseMath -import kscience.kmath.expressions.invoke -import kscience.kmath.operations.Complex -import kscience.kmath.operations.ComplexField +import scientifik.kmath.asm.compile +import scientifik.kmath.asm.expression +import scientifik.kmath.ast.mstInField +import scientifik.kmath.ast.parseMath +import scientifik.kmath.expressions.invoke +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.ComplexField import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/ast/ParserPrecedenceTest.kt b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserPrecedenceTest.kt similarity index 81% rename from kmath-ast/src/jvmTest/kotlin/kscience/kmath/ast/ParserPrecedenceTest.kt rename to kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserPrecedenceTest.kt index 1844a0991..9bdbb12c9 100644 --- a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/ast/ParserPrecedenceTest.kt +++ b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserPrecedenceTest.kt @@ -1,9 +1,9 @@ -package kscience.kmath.ast +package scietifik.kmath.ast -import kscience.kmath.ast.evaluate -import kscience.kmath.ast.parseMath -import kscience.kmath.operations.Field -import kscience.kmath.operations.RealField +import scientifik.kmath.ast.evaluate +import scientifik.kmath.ast.parseMath +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.RealField import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/ast/ParserTest.kt b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserTest.kt similarity index 80% rename from kmath-ast/src/jvmTest/kotlin/kscience/kmath/ast/ParserTest.kt rename to kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserTest.kt index 2dc24597e..9179c3428 100644 --- a/kmath-ast/src/jvmTest/kotlin/kscience/kmath/ast/ParserTest.kt +++ b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserTest.kt @@ -1,13 +1,13 @@ -package kscience.kmath.ast +package scietifik.kmath.ast -import kscience.kmath.ast.evaluate -import kscience.kmath.ast.mstInField -import kscience.kmath.ast.parseMath -import kscience.kmath.expressions.invoke -import kscience.kmath.operations.Algebra -import kscience.kmath.operations.Complex -import kscience.kmath.operations.ComplexField -import kscience.kmath.operations.RealField +import scientifik.kmath.ast.evaluate +import scientifik.kmath.ast.mstInField +import scientifik.kmath.ast.parseMath +import scientifik.kmath.expressions.invoke +import scientifik.kmath.operations.Algebra +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.ComplexField +import scientifik.kmath.operations.RealField import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 6a44c92f2..5ce1b935a 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -1,12 +1,13 @@ plugins { - id("ru.mipt.npm.jvm") + id("scientifik.jvm") } + description = "Commons math binding for kmath" dependencies { api(project(":kmath-core")) api(project(":kmath-coroutines")) - api(project(":kmath-stat")) + api(project(":kmath-prob")) api(project(":kmath-functions")) api("org.apache.commons:commons-math3:3.6.1") -} +} \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt b/kmath-commons/src/main/kotlin/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt deleted file mode 100644 index 345babe8b..000000000 --- a/kmath-commons/src/main/kotlin/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt +++ /dev/null @@ -1,121 +0,0 @@ -package kscience.kmath.commons.expressions - -import kscience.kmath.expressions.* -import kscience.kmath.operations.ExtendedField -import org.apache.commons.math3.analysis.differentiation.DerivativeStructure - -/** - * A field over commons-math [DerivativeStructure]. - * - * @property order The derivation order. - * @property bindings The map of bindings values. All bindings are considered free parameters - */ -public class DerivativeStructureField( - public val order: Int, - bindings: Map, -) : ExtendedField, ExpressionAlgebra { - public val numberOfVariables: Int = bindings.size - - public override val zero: DerivativeStructure by lazy { DerivativeStructure(numberOfVariables, order) } - public override val one: DerivativeStructure by lazy { DerivativeStructure(numberOfVariables, order, 1.0) } - - /** - * A class that implements both [DerivativeStructure] and a [Symbol] - */ - public inner class DerivativeStructureSymbol( - size: Int, - index: Int, - symbol: Symbol, - value: Double, - ) : DerivativeStructure(size, order, index, value), Symbol { - override val identity: String = symbol.identity - override fun toString(): String = identity - override fun equals(other: Any?): Boolean = this.identity == (other as? Symbol)?.identity - override fun hashCode(): Int = identity.hashCode() - } - - /** - * Identity-based symbol bindings map - */ - private val variables: Map = bindings.entries.mapIndexed { index, (key, value) -> - key.identity to DerivativeStructureSymbol(numberOfVariables, index, key, value) - }.toMap() - - override fun const(value: Double): DerivativeStructure = DerivativeStructure(numberOfVariables, order, value) - - public override fun bindOrNull(symbol: Symbol): DerivativeStructureSymbol? = variables[symbol.identity] - - public fun bind(symbol: Symbol): DerivativeStructureSymbol = variables.getValue(symbol.identity) - - override fun symbol(value: String): DerivativeStructureSymbol = bind(StringSymbol(value)) - - public fun DerivativeStructure.derivative(symbols: List): Double { - require(symbols.size <= order) { "The order of derivative ${symbols.size} exceeds computed order $order" } - val ordersCount = symbols.map { it.identity }.groupBy { it }.mapValues { it.value.size } - return getPartialDerivative(*variables.keys.map { ordersCount[it] ?: 0 }.toIntArray()) - } - - public fun DerivativeStructure.derivative(vararg symbols: Symbol): Double = derivative(symbols.toList()) - - public override fun add(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.add(b) - - public override fun multiply(a: DerivativeStructure, k: Number): DerivativeStructure = when (k) { - is Double -> a.multiply(k) - is Int -> a.multiply(k) - else -> a.multiply(k.toDouble()) - } - - public override fun multiply(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.multiply(b) - public override fun divide(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.divide(b) - public override fun sin(arg: DerivativeStructure): DerivativeStructure = arg.sin() - public override fun cos(arg: DerivativeStructure): DerivativeStructure = arg.cos() - public override fun tan(arg: DerivativeStructure): DerivativeStructure = arg.tan() - public override fun asin(arg: DerivativeStructure): DerivativeStructure = arg.asin() - public override fun acos(arg: DerivativeStructure): DerivativeStructure = arg.acos() - public override fun atan(arg: DerivativeStructure): DerivativeStructure = arg.atan() - public override fun sinh(arg: DerivativeStructure): DerivativeStructure = arg.sinh() - public override fun cosh(arg: DerivativeStructure): DerivativeStructure = arg.cosh() - public override fun tanh(arg: DerivativeStructure): DerivativeStructure = arg.tanh() - public override fun asinh(arg: DerivativeStructure): DerivativeStructure = arg.asinh() - public override fun acosh(arg: DerivativeStructure): DerivativeStructure = arg.acosh() - public override fun atanh(arg: DerivativeStructure): DerivativeStructure = arg.atanh() - - public override fun power(arg: DerivativeStructure, pow: Number): DerivativeStructure = when (pow) { - is Double -> arg.pow(pow) - is Int -> arg.pow(pow) - else -> arg.pow(pow.toDouble()) - } - - public fun power(arg: DerivativeStructure, pow: DerivativeStructure): DerivativeStructure = arg.pow(pow) - public override fun exp(arg: DerivativeStructure): DerivativeStructure = arg.exp() - public override fun ln(arg: DerivativeStructure): DerivativeStructure = arg.log() - - public override operator fun DerivativeStructure.plus(b: Number): DerivativeStructure = add(b.toDouble()) - public override operator fun DerivativeStructure.minus(b: Number): DerivativeStructure = subtract(b.toDouble()) - public override operator fun Number.plus(b: DerivativeStructure): DerivativeStructure = b + this - public override operator fun Number.minus(b: DerivativeStructure): DerivativeStructure = b - this - - public companion object : - AutoDiffProcessor> { - public override fun process(function: DerivativeStructureField.() -> DerivativeStructure): DifferentiableExpression> = - DerivativeStructureExpression(function) - } -} - - -/** - * A constructs that creates a derivative structure with required order on-demand - */ -public class DerivativeStructureExpression( - public val function: DerivativeStructureField.() -> DerivativeStructure, -) : DifferentiableExpression> { - public override operator fun invoke(arguments: Map): Double = - DerivativeStructureField(0, arguments).function().value - - /** - * Get the derivative expression with given orders - */ - public override fun derivativeOrNull(symbols: List): Expression = Expression { arguments -> - with(DerivativeStructureField(symbols.size, arguments)) { function().derivative(symbols) } - } -} diff --git a/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMMatrix.kt deleted file mode 100644 index 712927400..000000000 --- a/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMMatrix.kt +++ /dev/null @@ -1,92 +0,0 @@ -package kscience.kmath.commons.linear - -import kscience.kmath.linear.* -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.NDStructure -import org.apache.commons.math3.linear.* - -public class CMMatrix(public val origin: RealMatrix, features: Set? = null) : FeaturedMatrix { - public override val rowNum: Int get() = origin.rowDimension - public override val colNum: Int get() = origin.columnDimension - - public override val features: Set = features ?: sequence { - if (origin is DiagonalMatrix) yield(DiagonalFeature) - }.toHashSet() - - public override fun suggestFeature(vararg features: MatrixFeature): CMMatrix = - CMMatrix(origin, this.features + features) - - public override operator fun get(i: Int, j: Int): Double = origin.getEntry(i, j) - - public override fun equals(other: Any?): Boolean { - return NDStructure.equals(this, other as? NDStructure<*> ?: return false) - } - - public override fun hashCode(): Int { - var result = origin.hashCode() - result = 31 * result + features.hashCode() - return result - } -} - -public fun Matrix.toCM(): CMMatrix = if (this is CMMatrix) { - this -} else { - //TODO add feature analysis - val array = Array(rowNum) { i -> DoubleArray(colNum) { j -> get(i, j) } } - CMMatrix(Array2DRowRealMatrix(array)) -} - -public fun RealMatrix.asMatrix(): CMMatrix = CMMatrix(this) - -public class CMVector(public val origin: RealVector) : Point { - public override val size: Int get() = origin.dimension - - public override operator fun get(index: Int): Double = origin.getEntry(index) - - public override operator fun iterator(): Iterator = origin.toArray().iterator() -} - -public fun Point.toCM(): CMVector = if (this is CMVector) this else { - val array = DoubleArray(size) { this[it] } - CMVector(ArrayRealVector(array)) -} - -public fun RealVector.toPoint(): CMVector = CMVector(this) - -public object CMMatrixContext : MatrixContext { - public override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): CMMatrix { - val array = Array(rows) { i -> DoubleArray(columns) { j -> initializer(i, j) } } - return CMMatrix(Array2DRowRealMatrix(array)) - } - - public override fun Matrix.dot(other: Matrix): CMMatrix = - CMMatrix(toCM().origin.multiply(other.toCM().origin)) - - public override fun Matrix.dot(vector: Point): CMVector = - CMVector(toCM().origin.preMultiply(vector.toCM().origin)) - - public override operator fun Matrix.unaryMinus(): CMMatrix = - produce(rowNum, colNum) { i, j -> -get(i, j) } - - public override fun add(a: Matrix, b: Matrix): CMMatrix = - CMMatrix(a.toCM().origin.multiply(b.toCM().origin)) - - public override operator fun Matrix.minus(b: Matrix): CMMatrix = - CMMatrix(toCM().origin.subtract(b.toCM().origin)) - - public override fun multiply(a: Matrix, k: Number): CMMatrix = - CMMatrix(a.toCM().origin.scalarMultiply(k.toDouble())) - - public override operator fun Matrix.times(value: Double): CMMatrix = - produce(rowNum, colNum) { i, j -> get(i, j) * value } -} - -public operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = - CMMatrix(origin.add(other.origin)) - -public operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = - CMMatrix(origin.subtract(other.origin)) - -public infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = - CMMatrix(origin.multiply(other.origin)) diff --git a/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMSolver.kt b/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMSolver.kt deleted file mode 100644 index 210014e1a..000000000 --- a/kmath-commons/src/main/kotlin/kscience/kmath/commons/linear/CMSolver.kt +++ /dev/null @@ -1,41 +0,0 @@ -package kscience.kmath.commons.linear - -import kscience.kmath.linear.Point -import kscience.kmath.structures.Matrix -import org.apache.commons.math3.linear.* - -public enum class CMDecomposition { - LUP, - QR, - RRQR, - EIGEN, - CHOLESKY -} - -public fun CMMatrixContext.solver( - a: Matrix, - decomposition: CMDecomposition = CMDecomposition.LUP -): DecompositionSolver = when (decomposition) { - CMDecomposition.LUP -> LUDecomposition(a.toCM().origin).solver - CMDecomposition.RRQR -> RRQRDecomposition(a.toCM().origin).solver - CMDecomposition.QR -> QRDecomposition(a.toCM().origin).solver - CMDecomposition.EIGEN -> EigenDecomposition(a.toCM().origin).solver - CMDecomposition.CHOLESKY -> CholeskyDecomposition(a.toCM().origin).solver -} - -public fun CMMatrixContext.solve( - a: Matrix, - b: Matrix, - decomposition: CMDecomposition = CMDecomposition.LUP -): CMMatrix = solver(a, decomposition).solve(b.toCM().origin).asMatrix() - -public fun CMMatrixContext.solve( - a: Matrix, - b: Point, - decomposition: CMDecomposition = CMDecomposition.LUP -): CMVector = solver(a, decomposition).solve(b.toCM().origin).toPoint() - -public fun CMMatrixContext.inverse( - a: Matrix, - decomposition: CMDecomposition = CMDecomposition.LUP -): CMMatrix = solver(a, decomposition).inverse.asMatrix() diff --git a/kmath-commons/src/main/kotlin/kscience/kmath/commons/optimization/CMOptimizationProblem.kt b/kmath-commons/src/main/kotlin/kscience/kmath/commons/optimization/CMOptimizationProblem.kt deleted file mode 100644 index d6f79529a..000000000 --- a/kmath-commons/src/main/kotlin/kscience/kmath/commons/optimization/CMOptimizationProblem.kt +++ /dev/null @@ -1,110 +0,0 @@ -package kscience.kmath.commons.optimization - -import kscience.kmath.expressions.* -import kscience.kmath.stat.OptimizationFeature -import kscience.kmath.stat.OptimizationProblem -import kscience.kmath.stat.OptimizationProblemFactory -import kscience.kmath.stat.OptimizationResult -import org.apache.commons.math3.optim.* -import org.apache.commons.math3.optim.nonlinear.scalar.GoalType -import org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer -import org.apache.commons.math3.optim.nonlinear.scalar.ObjectiveFunction -import org.apache.commons.math3.optim.nonlinear.scalar.ObjectiveFunctionGradient -import org.apache.commons.math3.optim.nonlinear.scalar.gradient.NonLinearConjugateGradientOptimizer -import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.AbstractSimplex -import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.NelderMeadSimplex -import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.SimplexOptimizer -import kotlin.reflect.KClass - -public operator fun PointValuePair.component1(): DoubleArray = point -public operator fun PointValuePair.component2(): Double = value - -public class CMOptimizationProblem(override val symbols: List, ) : - OptimizationProblem, SymbolIndexer, OptimizationFeature { - private val optimizationData: HashMap, OptimizationData> = HashMap() - private var optimizatorBuilder: (() -> MultivariateOptimizer)? = null - public var convergenceChecker: ConvergenceChecker = SimpleValueChecker(DEFAULT_RELATIVE_TOLERANCE, - DEFAULT_ABSOLUTE_TOLERANCE, DEFAULT_MAX_ITER) - - public fun addOptimizationData(data: OptimizationData) { - optimizationData[data::class] = data - } - - init { - addOptimizationData(MaxEval.unlimited()) - } - - public fun exportOptimizationData(): List = optimizationData.values.toList() - - public override fun initialGuess(map: Map): Unit { - addOptimizationData(InitialGuess(map.toDoubleArray())) - } - - public override fun expression(expression: Expression): Unit { - val objectiveFunction = ObjectiveFunction { - val args = it.toMap() - expression(args) - } - addOptimizationData(objectiveFunction) - } - - public override fun diffExpression(expression: DifferentiableExpression>) { - expression(expression) - val gradientFunction = ObjectiveFunctionGradient { - val args = it.toMap() - DoubleArray(symbols.size) { index -> - expression.derivative(symbols[index])(args) - } - } - addOptimizationData(gradientFunction) - if (optimizatorBuilder == null) { - optimizatorBuilder = { - NonLinearConjugateGradientOptimizer( - NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES, - convergenceChecker - ) - } - } - } - - public fun simplex(simplex: AbstractSimplex) { - addOptimizationData(simplex) - //Set optimization builder to simplex if it is not present - if (optimizatorBuilder == null) { - optimizatorBuilder = { SimplexOptimizer(convergenceChecker) } - } - } - - public fun simplexSteps(steps: Map) { - simplex(NelderMeadSimplex(steps.toDoubleArray())) - } - - public fun goal(goalType: GoalType) { - addOptimizationData(goalType) - } - - public fun optimizer(block: () -> MultivariateOptimizer) { - optimizatorBuilder = block - } - - override fun update(result: OptimizationResult) { - initialGuess(result.point) - } - - override fun optimize(): OptimizationResult { - val optimizer = optimizatorBuilder?.invoke() ?: error("Optimizer not defined") - val (point, value) = optimizer.optimize(*optimizationData.values.toTypedArray()) - return OptimizationResult(point.toMap(), value, setOf(this)) - } - - public companion object : OptimizationProblemFactory { - public const val DEFAULT_RELATIVE_TOLERANCE: Double = 1e-4 - public const val DEFAULT_ABSOLUTE_TOLERANCE: Double = 1e-4 - public const val DEFAULT_MAX_ITER: Int = 1000 - - override fun build(symbols: List): CMOptimizationProblem = CMOptimizationProblem(symbols) - } -} - -public fun CMOptimizationProblem.initialGuess(vararg pairs: Pair): Unit = initialGuess(pairs.toMap()) -public fun CMOptimizationProblem.simplexSteps(vararg pairs: Pair): Unit = simplexSteps(pairs.toMap()) diff --git a/kmath-commons/src/main/kotlin/kscience/kmath/commons/optimization/cmFit.kt b/kmath-commons/src/main/kotlin/kscience/kmath/commons/optimization/cmFit.kt deleted file mode 100644 index b8e8bfd4b..000000000 --- a/kmath-commons/src/main/kotlin/kscience/kmath/commons/optimization/cmFit.kt +++ /dev/null @@ -1,67 +0,0 @@ -package kscience.kmath.commons.optimization - -import kscience.kmath.commons.expressions.DerivativeStructureField -import kscience.kmath.expressions.DifferentiableExpression -import kscience.kmath.expressions.Expression -import kscience.kmath.expressions.Symbol -import kscience.kmath.stat.Fitting -import kscience.kmath.stat.OptimizationResult -import kscience.kmath.stat.optimizeWith -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.asBuffer -import org.apache.commons.math3.analysis.differentiation.DerivativeStructure -import org.apache.commons.math3.optim.nonlinear.scalar.GoalType - -/** - * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation - */ -public fun Fitting.chiSquared( - x: Buffer, - y: Buffer, - yErr: Buffer, - model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure, -): DifferentiableExpression> = chiSquared(DerivativeStructureField, x, y, yErr, model) - -/** - * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation - */ -public fun Fitting.chiSquared( - x: Iterable, - y: Iterable, - yErr: Iterable, - model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure, -): DifferentiableExpression> = chiSquared( - DerivativeStructureField, - x.toList().asBuffer(), - y.toList().asBuffer(), - yErr.toList().asBuffer(), - model -) - -/** - * Optimize expression without derivatives - */ -public fun Expression.optimize( - vararg symbols: Symbol, - configuration: CMOptimizationProblem.() -> Unit, -): OptimizationResult = optimizeWith(CMOptimizationProblem, symbols = symbols, configuration) - -/** - * Optimize differentiable expression - */ -public fun DifferentiableExpression>.optimize( - vararg symbols: Symbol, - configuration: CMOptimizationProblem.() -> Unit, -): OptimizationResult = optimizeWith(CMOptimizationProblem, symbols = symbols, configuration) - -public fun DifferentiableExpression>.minimize( - vararg startPoint: Pair, - configuration: CMOptimizationProblem.() -> Unit = {}, -): OptimizationResult { - require(startPoint.isNotEmpty()) { "Must provide a list of symbols for optimization" } - val problem = CMOptimizationProblem(startPoint.map { it.first }).apply(configuration) - problem.diffExpression(this) - problem.initialGuess(startPoint.toMap()) - problem.goal(GoalType.MINIMIZE) - return problem.optimize() -} \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/kscience/kmath/commons/random/CMRandomGeneratorWrapper.kt b/kmath-commons/src/main/kotlin/kscience/kmath/commons/random/CMRandomGeneratorWrapper.kt deleted file mode 100644 index 1eab5f2bd..000000000 --- a/kmath-commons/src/main/kotlin/kscience/kmath/commons/random/CMRandomGeneratorWrapper.kt +++ /dev/null @@ -1,34 +0,0 @@ -package kscience.kmath.commons.random - -import kscience.kmath.stat.RandomGenerator - -public class CMRandomGeneratorWrapper( - public val factory: (IntArray) -> RandomGenerator, -) : org.apache.commons.math3.random.RandomGenerator { - private var generator: RandomGenerator = factory(intArrayOf()) - - public override fun nextBoolean(): Boolean = generator.nextBoolean() - public override fun nextFloat(): Float = generator.nextDouble().toFloat() - - public override fun setSeed(seed: Int) { - generator = factory(intArrayOf(seed)) - } - - public override fun setSeed(seed: IntArray) { - generator = factory(seed) - } - - public override fun setSeed(seed: Long) { - setSeed(seed.toInt()) - } - - public override fun nextBytes(bytes: ByteArray) { - generator.fillBytes(bytes) - } - - public override fun nextInt(): Int = generator.nextInt() - public override fun nextInt(n: Int): Int = generator.nextInt(n) - public override fun nextGaussian(): Double = TODO() - public override fun nextDouble(): Double = generator.nextDouble() - public override fun nextLong(): Long = generator.nextLong() -} diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/commons/expressions/DiffExpression.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/expressions/DiffExpression.kt new file mode 100644 index 000000000..64ebe8da3 --- /dev/null +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/expressions/DiffExpression.kt @@ -0,0 +1,145 @@ +package scientifik.kmath.commons.expressions + +import org.apache.commons.math3.analysis.differentiation.DerivativeStructure +import scientifik.kmath.expressions.Expression +import scientifik.kmath.expressions.ExpressionAlgebra +import scientifik.kmath.operations.ExtendedField +import scientifik.kmath.operations.Field +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * A field wrapping commons-math derivative structures + */ +class DerivativeStructureField( + val order: Int, + val parameters: Map +) : ExtendedField { + + override val zero: DerivativeStructure by lazy { DerivativeStructure(order, parameters.size) } + override val one: DerivativeStructure by lazy { DerivativeStructure(order, parameters.size, 1.0) } + + private val variables: Map = parameters.mapValues { (key, value) -> + DerivativeStructure(parameters.size, order, parameters.keys.indexOf(key), value) + } + + val variable = object : ReadOnlyProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): DerivativeStructure { + return variables[property.name] ?: error("A variable with name ${property.name} does not exist") + } + } + + fun variable(name: String, default: DerivativeStructure? = null): DerivativeStructure = + variables[name] ?: default ?: error("A variable with name $name does not exist") + + + fun Number.const() = DerivativeStructure(order, parameters.size, toDouble()) + + fun DerivativeStructure.deriv(parName: String, order: Int = 1): Double { + return deriv(mapOf(parName to order)) + } + + fun DerivativeStructure.deriv(orders: Map): Double { + return getPartialDerivative(*parameters.keys.map { orders[it] ?: 0 }.toIntArray()) + } + + fun DerivativeStructure.deriv(vararg orders: Pair): Double = deriv(mapOf(*orders)) + + override fun add(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.add(b) + + override fun multiply(a: DerivativeStructure, k: Number): DerivativeStructure = when (k) { + is Double -> a.multiply(k) + is Int -> a.multiply(k) + else -> a.multiply(k.toDouble()) + } + + override fun multiply(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.multiply(b) + + 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 tan(arg: DerivativeStructure): DerivativeStructure = arg.tan() + 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 sinh(arg: DerivativeStructure): DerivativeStructure = arg.sinh() + override fun cosh(arg: DerivativeStructure): DerivativeStructure = arg.cosh() + override fun tanh(arg: DerivativeStructure): DerivativeStructure = arg.tanh() + override fun asinh(arg: DerivativeStructure): DerivativeStructure = arg.asinh() + override fun acosh(arg: DerivativeStructure): DerivativeStructure = arg.acosh() + override fun atanh(arg: DerivativeStructure): DerivativeStructure = arg.atanh() + + override fun power(arg: DerivativeStructure, pow: Number): DerivativeStructure = when (pow) { + is Double -> arg.pow(pow) + is Int -> arg.pow(pow) + else -> arg.pow(pow.toDouble()) + } + + fun power(arg: DerivativeStructure, pow: DerivativeStructure): DerivativeStructure = arg.pow(pow) + override fun exp(arg: DerivativeStructure): DerivativeStructure = arg.exp() + override fun ln(arg: DerivativeStructure): DerivativeStructure = arg.log() + + override operator fun DerivativeStructure.plus(b: Number): DerivativeStructure = add(b.toDouble()) + override operator fun DerivativeStructure.minus(b: Number): DerivativeStructure = subtract(b.toDouble()) + override operator fun Number.plus(b: DerivativeStructure) = b + this + override operator fun Number.minus(b: DerivativeStructure) = b - this +} + +/** + * A constructs that creates a derivative structure with required order on-demand + */ +class DiffExpression(val function: DerivativeStructureField.() -> DerivativeStructure) : Expression { + + override fun invoke(arguments: Map): Double = DerivativeStructureField( + 0, + arguments + ).run(function).value + + /** + * Get the derivative expression with given orders + * TODO make result [DiffExpression] + */ + fun derivative(orders: Map): Expression { + return object : Expression { + override fun invoke(arguments: Map): Double = + DerivativeStructureField(orders.values.max() ?: 0, arguments) + .run { + function().deriv(orders) + } + } + } + + //TODO add gradient and maybe other vector operators +} + +fun DiffExpression.derivative(vararg orders: Pair) = derivative(mapOf(*orders)) +fun DiffExpression.derivative(name: String) = derivative(name to 1) + +/** + * A context for [DiffExpression] (not to be confused with [DerivativeStructure]) + */ +object DiffExpressionAlgebra : ExpressionAlgebra, Field { + override fun variable(name: String, default: Double?) = + DiffExpression { variable(name, default?.const()) } + + override fun const(value: Double): DiffExpression = + DiffExpression { value.const() } + + override fun add(a: DiffExpression, b: DiffExpression) = + DiffExpression { a.function(this) + b.function(this) } + + override val zero = DiffExpression { 0.0.const() } + + override fun multiply(a: DiffExpression, k: Number) = + DiffExpression { a.function(this) * k } + + override val one = DiffExpression { 1.0.const() } + + override fun multiply(a: DiffExpression, b: DiffExpression) = + DiffExpression { a.function(this) * b.function(this) } + + override fun divide(a: DiffExpression, b: DiffExpression) = + DiffExpression { a.function(this) / b.function(this) } +} diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/commons/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/linear/CMMatrix.kt new file mode 100644 index 000000000..a17effccc --- /dev/null +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/linear/CMMatrix.kt @@ -0,0 +1,96 @@ +package scientifik.kmath.commons.linear + +import org.apache.commons.math3.linear.* +import org.apache.commons.math3.linear.RealMatrix +import org.apache.commons.math3.linear.RealVector +import scientifik.kmath.linear.* +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.NDStructure + +class CMMatrix(val origin: RealMatrix, features: Set? = null) : + FeaturedMatrix { + override val rowNum: Int get() = origin.rowDimension + override val colNum: Int get() = origin.columnDimension + + override val features: Set = features ?: sequence { + if (origin is DiagonalMatrix) yield(DiagonalFeature) + }.toSet() + + override fun suggestFeature(vararg features: MatrixFeature) = + CMMatrix(origin, this.features + features) + + override fun get(i: Int, j: Int): Double = origin.getEntry(i, j) + + override fun equals(other: Any?): Boolean { + return NDStructure.equals(this, other as? NDStructure<*> ?: return false) + } + + override fun hashCode(): Int { + var result = origin.hashCode() + result = 31 * result + features.hashCode() + return result + } +} + +fun Matrix.toCM(): CMMatrix = if (this is CMMatrix) { + this +} else { + //TODO add feature analysis + val array = Array(rowNum) { i -> DoubleArray(colNum) { j -> get(i, j) } } + CMMatrix(Array2DRowRealMatrix(array)) +} + +fun RealMatrix.asMatrix() = CMMatrix(this) + +class CMVector(val origin: RealVector) : Point { + override val size: Int get() = origin.dimension + + override fun get(index: Int): Double = origin.getEntry(index) + + override fun iterator(): Iterator = origin.toArray().iterator() +} + +fun Point.toCM(): CMVector = if (this is CMVector) { + this +} else { + val array = DoubleArray(size) { this[it] } + CMVector(ArrayRealVector(array)) +} + +fun RealVector.toPoint() = CMVector(this) + +object CMMatrixContext : MatrixContext { + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): CMMatrix { + val array = Array(rows) { i -> DoubleArray(columns) { j -> initializer(i, j) } } + return CMMatrix(Array2DRowRealMatrix(array)) + } + + override fun Matrix.dot(other: Matrix) = + CMMatrix(this.toCM().origin.multiply(other.toCM().origin)) + + override fun Matrix.dot(vector: Point): CMVector = + CMVector(this.toCM().origin.preMultiply(vector.toCM().origin)) + + override fun Matrix.unaryMinus(): CMMatrix = + produce(rowNum, colNum) { i, j -> -get(i, j) } + + override fun add(a: Matrix, b: Matrix) = + CMMatrix(a.toCM().origin.multiply(b.toCM().origin)) + + override fun Matrix.minus(b: Matrix) = + CMMatrix(this.toCM().origin.subtract(b.toCM().origin)) + + override fun multiply(a: Matrix, k: Number) = + CMMatrix(a.toCM().origin.scalarMultiply(k.toDouble())) + + override fun Matrix.times(value: Double): Matrix = + produce(rowNum, colNum) { i, j -> get(i, j) * value } +} + +operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = + CMMatrix(this.origin.add(other.origin)) +operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = + CMMatrix(this.origin.subtract(other.origin)) + +infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = + CMMatrix(this.origin.multiply(other.origin)) \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/commons/linear/CMSolver.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/linear/CMSolver.kt new file mode 100644 index 000000000..77b688e31 --- /dev/null +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/linear/CMSolver.kt @@ -0,0 +1,40 @@ +package scientifik.kmath.commons.linear + +import org.apache.commons.math3.linear.* +import scientifik.kmath.linear.Point +import scientifik.kmath.structures.Matrix + +enum class CMDecomposition { + LUP, + QR, + RRQR, + EIGEN, + CHOLESKY +} + + +fun CMMatrixContext.solver(a: Matrix, decomposition: CMDecomposition = CMDecomposition.LUP) = + when (decomposition) { + CMDecomposition.LUP -> LUDecomposition(a.toCM().origin).solver + CMDecomposition.RRQR -> RRQRDecomposition(a.toCM().origin).solver + CMDecomposition.QR -> QRDecomposition(a.toCM().origin).solver + CMDecomposition.EIGEN -> EigenDecomposition(a.toCM().origin).solver + CMDecomposition.CHOLESKY -> CholeskyDecomposition(a.toCM().origin).solver + } + +fun CMMatrixContext.solve( + a: Matrix, + b: Matrix, + decomposition: CMDecomposition = CMDecomposition.LUP +) = solver(a, decomposition).solve(b.toCM().origin).asMatrix() + +fun CMMatrixContext.solve( + a: Matrix, + b: Point, + decomposition: CMDecomposition = CMDecomposition.LUP +) = solver(a, decomposition).solve(b.toCM().origin).toPoint() + +fun CMMatrixContext.inverse( + a: Matrix, + decomposition: CMDecomposition = CMDecomposition.LUP +) = solver(a, decomposition).inverse.asMatrix() diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/commons/random/CMRandomGeneratorWrapper.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/random/CMRandomGeneratorWrapper.kt new file mode 100644 index 000000000..13e79d60e --- /dev/null +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/random/CMRandomGeneratorWrapper.kt @@ -0,0 +1,38 @@ +package scientifik.kmath.commons.random + +import scientifik.kmath.prob.RandomGenerator + +class CMRandomGeneratorWrapper(val factory: (IntArray) -> RandomGenerator) : + org.apache.commons.math3.random.RandomGenerator { + private var generator = factory(intArrayOf()) + + override fun nextBoolean(): Boolean = generator.nextBoolean() + + override fun nextFloat(): Float = generator.nextDouble().toFloat() + + override fun setSeed(seed: Int) { + generator = factory(intArrayOf(seed)) + } + + override fun setSeed(seed: IntArray) { + generator = factory(seed) + } + + override fun setSeed(seed: Long) { + setSeed(seed.toInt()) + } + + override fun nextBytes(bytes: ByteArray) { + generator.fillBytes(bytes) + } + + override fun nextInt(): Int = generator.nextInt() + + override fun nextInt(n: Int): Int = generator.nextInt(n) + + override fun nextGaussian(): Double = TODO() + + override fun nextDouble(): Double = generator.nextDouble() + + override fun nextLong(): Long = generator.nextLong() +} \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/kscience/kmath/commons/transform/Transformations.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/transform/Transformations.kt similarity index 81% rename from kmath-commons/src/main/kotlin/kscience/kmath/commons/transform/Transformations.kt rename to kmath-commons/src/main/kotlin/scientifik/kmath/commons/transform/Transformations.kt index cd2896be6..eb1b5b69a 100644 --- a/kmath-commons/src/main/kotlin/kscience/kmath/commons/transform/Transformations.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/transform/Transformations.kt @@ -1,19 +1,20 @@ -package kscience.kmath.commons.transform +package scientifik.kmath.commons.transform import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kscience.kmath.operations.Complex -import kscience.kmath.streaming.chunked -import kscience.kmath.streaming.spread -import kscience.kmath.structures.* import org.apache.commons.math3.transform.* +import scientifik.kmath.operations.Complex +import scientifik.kmath.streaming.chunked +import scientifik.kmath.streaming.spread +import scientifik.kmath.structures.* /** * Streaming and buffer transformations */ -public object Transformations { +object Transformations { + private fun Buffer.toArray(): Array = Array(size) { org.apache.commons.math3.complex.Complex(get(it).re, get(it).im) } @@ -31,35 +32,35 @@ public object Transformations { Complex(value.real, value.imaginary) } - public fun fourier( + fun fourier( normalization: DftNormalization = DftNormalization.STANDARD, direction: TransformType = TransformType.FORWARD ): SuspendBufferTransform = { FastFourierTransformer(normalization).transform(it.toArray(), direction).asBuffer() } - public fun realFourier( + fun realFourier( normalization: DftNormalization = DftNormalization.STANDARD, direction: TransformType = TransformType.FORWARD ): SuspendBufferTransform = { FastFourierTransformer(normalization).transform(it.asArray(), direction).asBuffer() } - public fun sine( + fun sine( normalization: DstNormalization = DstNormalization.STANDARD_DST_I, direction: TransformType = TransformType.FORWARD ): SuspendBufferTransform = { FastSineTransformer(normalization).transform(it.asArray(), direction).asBuffer() } - public fun cosine( + fun cosine( normalization: DctNormalization = DctNormalization.STANDARD_DCT_I, direction: TransformType = TransformType.FORWARD ): SuspendBufferTransform = { FastCosineTransformer(normalization).transform(it.asArray(), direction).asBuffer() } - public fun hadamard( + fun hadamard( direction: TransformType = TransformType.FORWARD ): SuspendBufferTransform = { FastHadamardTransformer().transform(it.asArray(), direction).asBuffer() @@ -70,7 +71,7 @@ public object Transformations { * Process given [Flow] with commons-math fft transformation */ @FlowPreview -public fun Flow>.FFT( +fun Flow>.FFT( normalization: DftNormalization = DftNormalization.STANDARD, direction: TransformType = TransformType.FORWARD ): Flow> { @@ -80,7 +81,7 @@ public fun Flow>.FFT( @FlowPreview @JvmName("realFFT") -public fun Flow>.FFT( +fun Flow>.FFT( normalization: DftNormalization = DftNormalization.STANDARD, direction: TransformType = TransformType.FORWARD ): Flow> { @@ -89,18 +90,20 @@ public fun Flow>.FFT( } /** - * Process a continuous flow of real numbers in FFT splitting it in chunks of [bufferSize]. + * Process a continous flow of real numbers in FFT splitting it in chunks of [bufferSize]. */ @FlowPreview @JvmName("realFFT") -public fun Flow.FFT( +fun Flow.FFT( bufferSize: Int = Int.MAX_VALUE, normalization: DftNormalization = DftNormalization.STANDARD, direction: TransformType = TransformType.FORWARD -): Flow = chunked(bufferSize).FFT(normalization, direction).spread() +): Flow { + return chunked(bufferSize).FFT(normalization,direction).spread() +} /** * Map a complex flow into real flow by taking real part of each number */ @FlowPreview -public fun Flow.real(): Flow = map { it.re } +fun Flow.real(): Flow = map{it.re} \ No newline at end of file diff --git a/kmath-commons/src/test/kotlin/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt b/kmath-commons/src/test/kotlin/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt deleted file mode 100644 index 7511a38ed..000000000 --- a/kmath-commons/src/test/kotlin/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package kscience.kmath.commons.expressions - -import kscience.kmath.expressions.* -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFails - -internal inline fun diff( - order: Int, - vararg parameters: Pair, - block: DerivativeStructureField.() -> Unit, -): Unit { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - DerivativeStructureField(order, mapOf(*parameters)).run(block) -} - -internal class AutoDiffTest { - private val x by symbol - private val y by symbol - - @Test - fun derivativeStructureFieldTest() { - diff(2, x to 1.0, y to 1.0) { - val x = bind(x)//by binding() - val y = symbol("y") - val z = x * (-sin(x * y) + y) + 2.0 - println(z.derivative(x)) - println(z.derivative(y,x)) - assertEquals(z.derivative(x, y), z.derivative(y, x)) - //check that improper order cause failure - assertFails { z.derivative(x,x,y) } - } - } - - @Test - fun autoDifTest() { - val f = DerivativeStructureExpression { - val x by binding() - val y by binding() - x.pow(2) + 2 * x * y + y.pow(2) + 1 - } - - assertEquals(10.0, f(x to 1.0, y to 2.0)) - assertEquals(6.0, f.derivative(x)(x to 1.0, y to 2.0)) - assertEquals(2.0, f.derivative(x, x)(x to 1.234, y to -2.0)) - assertEquals(2.0, f.derivative(x, y)(x to 1.0, y to 2.0)) - } -} diff --git a/kmath-commons/src/test/kotlin/kscience/kmath/commons/optimization/OptimizeTest.kt b/kmath-commons/src/test/kotlin/kscience/kmath/commons/optimization/OptimizeTest.kt deleted file mode 100644 index 3290c8f32..000000000 --- a/kmath-commons/src/test/kotlin/kscience/kmath/commons/optimization/OptimizeTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package kscience.kmath.commons.optimization - -import kscience.kmath.commons.expressions.DerivativeStructureExpression -import kscience.kmath.expressions.symbol -import kscience.kmath.stat.Distribution -import kscience.kmath.stat.Fitting -import kscience.kmath.stat.RandomGenerator -import kscience.kmath.stat.normal -import org.junit.jupiter.api.Test -import kotlin.math.pow - -internal class OptimizeTest { - val x by symbol - val y by symbol - - val normal = DerivativeStructureExpression { - exp(-bind(x).pow(2) / 2) + exp(-bind(y).pow(2) / 2) - } - - @Test - fun testGradientOptimization() { - val result = normal.optimize(x, y) { - initialGuess(x to 1.0, y to 1.0) - //no need to select optimizer. Gradient optimizer is used by default because gradients are provided by function - } - println(result.point) - println(result.value) - } - - @Test - fun testSimplexOptimization() { - val result = normal.optimize(x, y) { - initialGuess(x to 1.0, y to 1.0) - simplexSteps(x to 2.0, y to 0.5) - //this sets simplex optimizer - } - println(result.point) - println(result.value) - } - - @Test - fun testCmFit() { - val a by symbol - val b by symbol - val c by symbol - - val sigma = 1.0 - val generator = Distribution.normal(0.0, sigma) - val chain = generator.sample(RandomGenerator.default(112667)) - val x = (1..100).map(Int::toDouble) - - val y = x.map { - it.pow(2) + it + 1 + chain.nextDouble() - } - - val yErr = List(x.size) { sigma } - - val chi2 = Fitting.chiSquared(x, y, yErr) { x1 -> - val cWithDefault = bindOrNull(c) ?: one - bind(a) * x1.pow(2) + bind(b) * x1 + cWithDefault - } - - val result = chi2.minimize(a to 1.5, b to 0.9, c to 1.0) - println(result) - println("Chi2/dof = ${result.value / (x.size - 3)}") - } - -} \ No newline at end of file diff --git a/kmath-commons/src/test/kotlin/scientifik/kmath/commons/expressions/AutoDiffTest.kt b/kmath-commons/src/test/kotlin/scientifik/kmath/commons/expressions/AutoDiffTest.kt new file mode 100644 index 000000000..c77f30eb7 --- /dev/null +++ b/kmath-commons/src/test/kotlin/scientifik/kmath/commons/expressions/AutoDiffTest.kt @@ -0,0 +1,32 @@ +package scientifik.kmath.commons.expressions + +import scientifik.kmath.expressions.invoke +import kotlin.test.Test +import kotlin.test.assertEquals + +inline fun diff(order: Int, vararg parameters: Pair, block: DerivativeStructureField.() -> R) = + DerivativeStructureField(order, mapOf(*parameters)).run(block) + +class AutoDiffTest { + @Test + fun derivativeStructureFieldTest() { + val res = diff(3, "x" to 1.0, "y" to 1.0) { + val x by variable + val y = variable("y") + val z = x * (-sin(x * y) + y) + z.deriv("x") + } + } + + @Test + fun autoDifTest() { + val f = DiffExpression { + val x by variable + val y by variable + x.pow(2) + 2 * x * y + y.pow(2) + 1 + } + + assertEquals(10.0, f("x" to 1.0, "y" to 2.0)) + assertEquals(6.0, f.derivative("x")("x" to 1.0, "y" to 2.0)) + } +} \ No newline at end of file diff --git a/kmath-core/README.md b/kmath-core/README.md index 3a919e85a..24e2c57d3 100644 --- a/kmath-core/README.md +++ b/kmath-core/README.md @@ -1,49 +1,40 @@ -# The Core Module (`kmath-core`) +# The Core Module (`kmath-ast`) The core features of KMath: - - [algebras](src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt) : Algebraic structures: contexts and elements - - [nd](src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt) : Many-dimensional structures - - [buffers](src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt) : One-dimensional structure - - [expressions](src/commonMain/kotlin/kscience/kmath/expressions) : Functional Expressions - - [domains](src/commonMain/kotlin/kscience/kmath/domains) : Domains - - [autodif](src/commonMain/kotlin/kscience/kmath/expressions/SimpleAutoDiff.kt) : Automatic differentiation - +- Algebraic structures: contexts and elements. +- ND structures. +- Buffers. +- Functional Expressions. +- Domains. +- Automatic differentiation. > #### Artifact: -> -> This module artifact: `kscience.kmath:kmath-core:0.2.0-dev-4`. -> -> Bintray release version: [ ![Download](https://api.bintray.com/packages/mipt-npm/kscience/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/kscience/kmath-core/_latestVersion) -> -> Bintray development version: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/dev/kmath-core/_latestVersion) -> +> This module is distributed in the artifact `scientifik:kmath-core:0.1.4-dev-8`. +> > **Gradle:** > > ```gradle > repositories { -> maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } -> maven { url 'https://dl.bintray.com/mipt-npm/kscience' } +> maven { url 'https://dl.bintray.com/mipt-npm/scientifik' } > maven { url 'https://dl.bintray.com/mipt-npm/dev' } -> maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - +> maven { url https://dl.bintray.com/hotkeytlt/maven' } > } > > dependencies { -> implementation 'kscience.kmath:kmath-core:0.2.0-dev-4' +> implementation 'scientifik:kmath-core:0.1.4-dev-8' > } > ``` > **Gradle Kotlin DSL:** > > ```kotlin > repositories { -> maven("https://dl.bintray.com/kotlin/kotlin-eap") -> maven("https://dl.bintray.com/mipt-npm/kscience") +> maven("https://dl.bintray.com/mipt-npm/scientifik") > maven("https://dl.bintray.com/mipt-npm/dev") > maven("https://dl.bintray.com/hotkeytlt/maven") > } > -> dependencies { -> implementation("kscience.kmath:kmath-core:0.2.0-dev-4") +> dependencies {`` +> implementation("scientifik:kmath-core:0.1.4-dev-8") > } > ``` diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 9ed7e690b..bea0fbf42 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -1,52 +1,7 @@ -plugins { - id("ru.mipt.npm.mpp") - id("ru.mipt.npm.native") -} +plugins { id("scientifik.mpp") } -kotlin.sourceSets.commonMain { - dependencies { - api(project(":kmath-memory")) +kotlin.sourceSets { + commonMain { + dependencies { api(project(":kmath-memory")) } } } - -readme { - description = "Core classes, algebra definitions, basic linear algebra" - maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT - propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) - - feature( - id = "algebras", - description = "Algebraic structures: contexts and elements", - ref = "src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt" - ) - - feature( - id = "nd", - description = "Many-dimensional structures", - ref = "src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt" - ) - - feature( - id = "buffers", - description = "One-dimensional structure", - ref = "src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt" - ) - - feature( - id = "expressions", - description = "Functional Expressions", - ref = "src/commonMain/kotlin/kscience/kmath/expressions" - ) - - feature( - id = "domains", - description = "Domains", - ref = "src/commonMain/kotlin/kscience/kmath/domains" - ) - - feature( - id = "autodif", - description = "Automatic differentiation", - ref = "src/commonMain/kotlin/kscience/kmath/expressions/SimpleAutoDiff.kt" - ) -} diff --git a/kmath-core/docs/README-TEMPLATE.md b/kmath-core/docs/README-TEMPLATE.md deleted file mode 100644 index 83d1ebdce..000000000 --- a/kmath-core/docs/README-TEMPLATE.md +++ /dev/null @@ -1,7 +0,0 @@ -# The Core Module (`kmath-core`) - -The core features of KMath: - -${features} - -${artifact} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/UnconstrainedDomain.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/UnconstrainedDomain.kt deleted file mode 100644 index e2efb51ab..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/UnconstrainedDomain.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015 Alexander Nozik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package kscience.kmath.domains - -import kscience.kmath.linear.Point - -public class UnconstrainedDomain(public override val dimension: Int) : RealDomain { - public override operator fun contains(point: Point): Boolean = true - - public override fun getLowerBound(num: Int, point: Point): Double? = Double.NEGATIVE_INFINITY - - public override fun getLowerBound(num: Int): Double? = Double.NEGATIVE_INFINITY - - public override fun getUpperBound(num: Int, point: Point): Double? = Double.POSITIVE_INFINITY - - public override fun getUpperBound(num: Int): Double? = Double.POSITIVE_INFINITY - - public override fun nearestInDomain(point: Point): Point = point - - public override fun volume(): Double = Double.POSITIVE_INFINITY -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/UnivariateDomain.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/UnivariateDomain.kt deleted file mode 100644 index bf090f2e5..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/UnivariateDomain.kt +++ /dev/null @@ -1,49 +0,0 @@ -package kscience.kmath.domains - -import kscience.kmath.linear.Point -import kscience.kmath.structures.asBuffer - -public inline class UnivariateDomain(public val range: ClosedFloatingPointRange) : RealDomain { - public override val dimension: Int - get() = 1 - - public operator fun contains(d: Double): Boolean = range.contains(d) - - public override operator fun contains(point: Point): Boolean { - require(point.size == 0) - return contains(point[0]) - } - - public override fun nearestInDomain(point: Point): Point { - require(point.size == 1) - val value = point[0] - - return when { - value in range -> point - value >= range.endInclusive -> doubleArrayOf(range.endInclusive).asBuffer() - else -> doubleArrayOf(range.start).asBuffer() - } - } - - public override fun getLowerBound(num: Int, point: Point): Double? { - require(num == 0) - return range.start - } - - public override fun getUpperBound(num: Int, point: Point): Double? { - require(num == 0) - return range.endInclusive - } - - public override fun getLowerBound(num: Int): Double? { - require(num == 0) - return range.start - } - - public override fun getUpperBound(num: Int): Double? { - require(num == 0) - return range.endInclusive - } - - public override fun volume(): Double = range.endInclusive - range.start -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/DifferentiableExpression.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/DifferentiableExpression.kt deleted file mode 100644 index abce9c4ec..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/DifferentiableExpression.kt +++ /dev/null @@ -1,48 +0,0 @@ -package kscience.kmath.expressions - -/** - * Represents expression which structure can be differentiated. - * - * @param T the type this expression takes as argument and returns. - * @param R the type of expression this expression can be differentiated to. - */ -public interface DifferentiableExpression> : Expression { - /** - * Differentiates this expression by ordered collection of [symbols]. - * - * @param symbols the symbols. - * @return the derivative or `null`. - */ - public fun derivativeOrNull(symbols: List): R? -} - -public fun > DifferentiableExpression.derivative(symbols: List): R = - derivativeOrNull(symbols) ?: error("Derivative by symbols $symbols not provided") - -public fun > DifferentiableExpression.derivative(vararg symbols: Symbol): R = - derivative(symbols.toList()) - -public fun > DifferentiableExpression.derivative(name: String): R = - derivative(StringSymbol(name)) - -/** - * A [DifferentiableExpression] that defines only first derivatives - */ -public abstract class FirstDerivativeExpression> : DifferentiableExpression { - /** - * Returns first derivative of this expression by given [symbol]. - */ - public abstract fun derivativeOrNull(symbol: Symbol): R? - - public final override fun derivativeOrNull(symbols: List): R? { - val dSymbol = symbols.firstOrNull() ?: return null - return derivativeOrNull(dSymbol) - } -} - -/** - * A factory that converts an expression in autodiff variables to a [DifferentiableExpression] - */ -public fun interface AutoDiffProcessor, out R : Expression> { - public fun process(function: A.() -> I): DifferentiableExpression -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/Expression.kt deleted file mode 100644 index 63bbc9312..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/Expression.kt +++ /dev/null @@ -1,115 +0,0 @@ -package kscience.kmath.expressions - -import kscience.kmath.operations.Algebra -import kotlin.jvm.JvmName -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -/** - * A marker interface for a symbol. A symbol mus have an identity - */ -public interface Symbol { - /** - * Identity object for the symbol. Two symbols with the same identity are considered to be the same symbol. - */ - public val identity: String - - public companion object : ReadOnlyProperty { - //TODO deprecate and replace by top level function after fix of https://youtrack.jetbrains.com/issue/KT-40121 - override fun getValue(thisRef: Any?, property: KProperty<*>): Symbol { - return StringSymbol(property.name) - } - } -} - -/** - * A [Symbol] with a [String] identity - */ -public inline class StringSymbol(override val identity: String) : Symbol { - override fun toString(): String = identity -} - -/** - * An elementary function that could be invoked on a map of arguments. - * - * @param T the type this expression takes as argument and returns. - */ -public fun interface Expression { - /** - * Calls this expression from arguments. - * - * @param arguments the map of arguments. - * @return the value. - */ - public operator fun invoke(arguments: Map): T -} - -/** - * Calls this expression without providing any arguments. - * - * @return a value. - */ -public operator fun Expression.invoke(): T = invoke(emptyMap()) - -/** - * Calls this expression from arguments. - * - * @param pairs the pairs of arguments to values. - * @return a value. - */ -@JvmName("callBySymbol") -public operator fun Expression.invoke(vararg pairs: Pair): T = invoke(mapOf(*pairs)) - -/** - * Calls this expression from arguments. - * - * @param pairs the pairs of arguments' names to values. - * @return a value. - */ -@JvmName("callByString") -public operator fun Expression.invoke(vararg pairs: Pair): T = - invoke(mapOf(*pairs).mapKeys { StringSymbol(it.key) }) - - -/** - * A context for expression construction - * - * @param T type of the constants for the expression - * @param E type of the actual expression state - */ -public interface ExpressionAlgebra : Algebra { - /** - * Bind a given [Symbol] to this context variable and produce context-specific object. Return null if symbol could not be bound in current context. - */ - public fun bindOrNull(symbol: Symbol): E? - - /** - * Bind a string to a context using [StringSymbol] - */ - override fun symbol(value: String): E = bind(StringSymbol(value)) - - /** - * A constant expression which does not depend on arguments - */ - public fun const(value: T): E -} - -/** - * Bind a given [Symbol] to this context variable and produce context-specific object. - */ -public fun ExpressionAlgebra.bind(symbol: Symbol): E = - bindOrNull(symbol) ?: error("Symbol $symbol could not be bound to $this") - -/** - * A delegate to create a symbol with a string identity in this scope - */ -public val symbol: ReadOnlyProperty get() = Symbol -//TODO does not work directly on native due to https://youtrack.jetbrains.com/issue/KT-40121 - - -/** - * Bind a symbol by name inside the [ExpressionAlgebra] - */ -public fun ExpressionAlgebra.binding(): ReadOnlyProperty = ReadOnlyProperty { _, property -> - bind(StringSymbol(property.name)) ?: error("A variable with name ${property.name} does not exist") -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/FunctionalExpressionAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/FunctionalExpressionAlgebra.kt deleted file mode 100644 index 0630e8e4b..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/FunctionalExpressionAlgebra.kt +++ /dev/null @@ -1,159 +0,0 @@ -package kscience.kmath.expressions - -import kscience.kmath.operations.* - -/** - * A context class for [Expression] construction. - * - * @param algebra The algebra to provide for Expressions built. - */ -public abstract class FunctionalExpressionAlgebra>( - public val algebra: A, -) : ExpressionAlgebra> { - /** - * Builds an Expression of constant expression which does not depend on arguments. - */ - public override fun const(value: T): Expression = Expression { value } - - /** - * Builds an Expression to access a variable. - */ - public override fun bindOrNull(symbol: Symbol): Expression? = Expression { arguments -> - arguments[symbol] ?: error("Argument not found: $symbol") - } - - /** - * Builds an Expression of dynamic call of binary operation [operation] on [left] and [right]. - */ - public override fun binaryOperation( - operation: String, - left: Expression, - right: Expression, - ): Expression = Expression { arguments -> - algebra.binaryOperation(operation, left.invoke(arguments), right.invoke(arguments)) - } - - /** - * Builds an Expression of dynamic call of unary operation with name [operation] on [arg]. - */ - public override fun unaryOperation(operation: String, arg: Expression): Expression = Expression { arguments -> - algebra.unaryOperation(operation, arg.invoke(arguments)) - } -} - -/** - * A context class for [Expression] construction for [Space] algebras. - */ -public open class FunctionalExpressionSpace>(algebra: A) : - FunctionalExpressionAlgebra(algebra), Space> { - public override val zero: Expression get() = const(algebra.zero) - - /** - * Builds an Expression of addition of two another expressions. - */ - public override fun add(a: Expression, b: Expression): Expression = - binaryOperation(SpaceOperations.PLUS_OPERATION, a, b) - - /** - * Builds an Expression of multiplication of expression by number. - */ - public override fun multiply(a: Expression, k: Number): Expression = Expression { arguments -> - algebra.multiply(a.invoke(arguments), k) - } - - public operator fun Expression.plus(arg: T): Expression = this + const(arg) - public operator fun Expression.minus(arg: T): Expression = this - const(arg) - public operator fun T.plus(arg: Expression): Expression = arg + this - public operator fun T.minus(arg: Expression): Expression = arg - this - - public override fun unaryOperation(operation: String, arg: Expression): Expression = - super.unaryOperation(operation, arg) - - public override fun binaryOperation(operation: String, left: Expression, right: Expression): Expression = - super.binaryOperation(operation, left, right) -} - -public open class FunctionalExpressionRing(algebra: A) : FunctionalExpressionSpace(algebra), - Ring> where A : Ring, A : NumericAlgebra { - public override val one: Expression - get() = const(algebra.one) - - /** - * Builds an Expression of multiplication of two expressions. - */ - public override fun multiply(a: Expression, b: Expression): Expression = - binaryOperation(RingOperations.TIMES_OPERATION, a, b) - - public operator fun Expression.times(arg: T): Expression = this * const(arg) - public operator fun T.times(arg: Expression): Expression = arg * this - - public override fun unaryOperation(operation: String, arg: Expression): Expression = - super.unaryOperation(operation, arg) - - public override fun binaryOperation(operation: String, left: Expression, right: Expression): Expression = - super.binaryOperation(operation, left, right) -} - -public open class FunctionalExpressionField(algebra: A) : - FunctionalExpressionRing(algebra), Field> - where A : Field, A : NumericAlgebra { - /** - * Builds an Expression of division an expression by another one. - */ - public override fun divide(a: Expression, b: Expression): Expression = - binaryOperation(FieldOperations.DIV_OPERATION, a, b) - - public operator fun Expression.div(arg: T): Expression = this / const(arg) - public operator fun T.div(arg: Expression): Expression = arg / this - - public override fun unaryOperation(operation: String, arg: Expression): Expression = - super.unaryOperation(operation, arg) - - public override fun binaryOperation(operation: String, left: Expression, right: Expression): Expression = - super.binaryOperation(operation, left, right) -} - -public open class FunctionalExpressionExtendedField(algebra: A) : - FunctionalExpressionField(algebra), - ExtendedField> where A : ExtendedField, A : NumericAlgebra { - public override fun sin(arg: Expression): Expression = - unaryOperation(TrigonometricOperations.SIN_OPERATION, arg) - - public override fun cos(arg: Expression): Expression = - unaryOperation(TrigonometricOperations.COS_OPERATION, arg) - - public override fun asin(arg: Expression): Expression = - unaryOperation(TrigonometricOperations.ASIN_OPERATION, arg) - - public override fun acos(arg: Expression): Expression = - unaryOperation(TrigonometricOperations.ACOS_OPERATION, arg) - - public override fun atan(arg: Expression): Expression = - unaryOperation(TrigonometricOperations.ATAN_OPERATION, arg) - - public override fun power(arg: Expression, pow: Number): Expression = - binaryOperation(PowerOperations.POW_OPERATION, arg, number(pow)) - - public override fun exp(arg: Expression): Expression = - unaryOperation(ExponentialOperations.EXP_OPERATION, arg) - - public override fun ln(arg: Expression): Expression = unaryOperation(ExponentialOperations.LN_OPERATION, arg) - - public override fun unaryOperation(operation: String, arg: Expression): Expression = - super.unaryOperation(operation, arg) - - public override fun binaryOperation(operation: String, left: Expression, right: Expression): Expression = - super.binaryOperation(operation, left, right) -} - -public inline fun > A.expressionInSpace(block: FunctionalExpressionSpace.() -> Expression): Expression = - FunctionalExpressionSpace(this).block() - -public inline fun > A.expressionInRing(block: FunctionalExpressionRing.() -> Expression): Expression = - FunctionalExpressionRing(this).block() - -public inline fun > A.expressionInField(block: FunctionalExpressionField.() -> Expression): Expression = - FunctionalExpressionField(this).block() - -public inline fun > A.expressionInExtendedField(block: FunctionalExpressionExtendedField.() -> Expression): Expression = - FunctionalExpressionExtendedField(this).block() diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SimpleAutoDiff.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SimpleAutoDiff.kt deleted file mode 100644 index e8a894d23..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SimpleAutoDiff.kt +++ /dev/null @@ -1,393 +0,0 @@ -package kscience.kmath.expressions - -import kscience.kmath.linear.Point -import kscience.kmath.operations.* -import kscience.kmath.structures.asBuffer -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -/* - * Implementation of backward-mode automatic differentiation. - * Initial gist by Roman Elizarov: https://gist.github.com/elizarov/1ad3a8583e88cb6ea7a0ad09bb591d3d - */ - - -public open class AutoDiffValue(public val value: T) - -/** - * Represents result of [simpleAutoDiff] call. - * - * @param T the non-nullable type of value. - * @param value the value of result. - * @property simpleAutoDiff The mapping of differentiated variables to their derivatives. - * @property context The field over [T]. - */ -public class DerivationResult( - public val value: T, - private val derivativeValues: Map, - public val context: Field, -) { - /** - * Returns derivative of [variable] or returns [Ring.zero] in [context]. - */ - public fun derivative(variable: Symbol): T = derivativeValues[variable.identity] ?: context.zero - - /** - * Computes the divergence. - */ - public fun div(): T = context { sum(derivativeValues.values) } -} - -/** - * Computes the gradient for variables in given order. - */ -public fun DerivationResult.grad(vararg variables: Symbol): Point { - check(variables.isNotEmpty()) { "Variable order is not provided for gradient construction" } - return variables.map(::derivative).asBuffer() -} - -/** - * Runs differentiation and establishes [SimpleAutoDiffField] context inside the block of code. - * - * The partial derivatives are placed in argument `d` variable - * - * Example: - * ``` - * val x by symbol // define variable(s) and their values - * val y = RealField.withAutoDiff() { sqr(x) + 5 * x + 3 } // write formulate in deriv context - * assertEquals(17.0, y.x) // the value of result (y) - * assertEquals(9.0, x.d) // dy/dx - * ``` - * - * @param body the action in [SimpleAutoDiffField] context returning [AutoDiffVariable] to differentiate with respect to. - * @return the result of differentiation. - */ -public fun > F.simpleAutoDiff( - bindings: Map, - body: SimpleAutoDiffField.() -> AutoDiffValue, -): DerivationResult { - contract { callsInPlace(body, InvocationKind.EXACTLY_ONCE) } - - return SimpleAutoDiffField(this, bindings).differentiate(body) -} - -public fun > F.simpleAutoDiff( - vararg bindings: Pair, - body: SimpleAutoDiffField.() -> AutoDiffValue, -): DerivationResult = simpleAutoDiff(bindings.toMap(), body) - -/** - * Represents field in context of which functions can be derived. - */ -public open class SimpleAutoDiffField>( - public val context: F, - bindings: Map, -) : Field>, ExpressionAlgebra> { - public override val zero: AutoDiffValue - get() = const(context.zero) - - public override val one: AutoDiffValue - get() = const(context.one) - - // this stack contains pairs of blocks and values to apply them to - private var stack: Array = arrayOfNulls(8) - private var sp: Int = 0 - private val derivatives: MutableMap, T> = hashMapOf() - - private val bindings: Map> = bindings.entries.associate { - it.key.identity to AutoDiffVariableWithDerivative(it.key.identity, it.value, context.zero) - } - - /** - * Differentiable variable with value and derivative of differentiation ([simpleAutoDiff]) result - * with respect to this variable. - * - * @param T the non-nullable type of value. - * @property value The value of this variable. - */ - private class AutoDiffVariableWithDerivative( - override val identity: String, - value: T, - var d: T, - ) : AutoDiffValue(value), Symbol { - override fun toString(): String = identity - override fun equals(other: Any?): Boolean = this.identity == (other as? Symbol)?.identity - override fun hashCode(): Int = identity.hashCode() - } - - public override fun bindOrNull(symbol: Symbol): AutoDiffValue? = bindings[symbol.identity] - - private fun getDerivative(variable: AutoDiffValue): T = - (variable as? AutoDiffVariableWithDerivative)?.d ?: derivatives[variable] ?: context.zero - - private fun setDerivative(variable: AutoDiffValue, value: T) { - if (variable is AutoDiffVariableWithDerivative) variable.d = value else derivatives[variable] = value - } - - @Suppress("UNCHECKED_CAST") - private fun runBackwardPass() { - while (sp > 0) { - val value = stack[--sp] - val block = stack[--sp] as F.(Any?) -> Unit - context.block(value) - } - } - - override fun const(value: T): AutoDiffValue = AutoDiffValue(value) - - /** - * A variable accessing inner state of derivatives. - * Use this value in inner builders to avoid creating additional derivative bindings. - */ - public var AutoDiffValue.d: T - get() = getDerivative(this) - set(value) = setDerivative(this, value) - - public inline fun const(block: F.() -> T): AutoDiffValue = const(context.block()) - - /** - * Performs update of derivative after the rest of the formula in the back-pass. - * - * For example, implementation of `sin` function is: - * - * ``` - * fun AD.sin(x: Variable): Variable = derive(Variable(sin(x.x)) { z -> // call derive with function result - * x.d += z.d * cos(x.x) // update derivative using chain rule and derivative of the function - * } - * ``` - */ - @Suppress("UNCHECKED_CAST") - public fun derive(value: R, block: F.(R) -> Unit): R { - // save block to stack for backward pass - if (sp >= stack.size) stack = stack.copyOf(stack.size * 2) - stack[sp++] = block - stack[sp++] = value - return value - } - - - internal fun differentiate(function: SimpleAutoDiffField.() -> AutoDiffValue): DerivationResult { - val result = function() - result.d = context.one // computing derivative w.r.t result - runBackwardPass() - return DerivationResult(result.value, bindings.mapValues { it.value.d }, context) - } - - // Overloads for Double constants - - public override operator fun Number.plus(b: AutoDiffValue): AutoDiffValue = - derive(const { this@plus.toDouble() * one + b.value }) { z -> - b.d += z.d - } - - public override operator fun AutoDiffValue.plus(b: Number): AutoDiffValue = b.plus(this) - - public override operator fun Number.minus(b: AutoDiffValue): AutoDiffValue = - derive(const { this@minus.toDouble() * one - b.value }) { z -> b.d -= z.d } - - public override operator fun AutoDiffValue.minus(b: Number): AutoDiffValue = - derive(const { this@minus.value - one * b.toDouble() }) { z -> this@minus.d += z.d } - - - // Basic math (+, -, *, /) - - public override fun add(a: AutoDiffValue, b: AutoDiffValue): AutoDiffValue = - derive(const { a.value + b.value }) { z -> - a.d += z.d - b.d += z.d - } - - public override fun multiply(a: AutoDiffValue, b: AutoDiffValue): AutoDiffValue = - derive(const { a.value * b.value }) { z -> - a.d += z.d * b.value - b.d += z.d * a.value - } - - public override fun divide(a: AutoDiffValue, b: AutoDiffValue): AutoDiffValue = - derive(const { a.value / b.value }) { z -> - a.d += z.d / b.value - b.d -= z.d * a.value / (b.value * b.value) - } - - public override fun multiply(a: AutoDiffValue, k: Number): AutoDiffValue = - derive(const { k.toDouble() * a.value }) { z -> - a.d += z.d * k.toDouble() - } -} - -/** - * A constructs that creates a derivative structure with required order on-demand - */ -public class SimpleAutoDiffExpression>( - public val field: F, - public val function: SimpleAutoDiffField.() -> AutoDiffValue, -) : FirstDerivativeExpression>() { - public override operator fun invoke(arguments: Map): T { - //val bindings = arguments.entries.map { it.key.bind(it.value) } - return SimpleAutoDiffField(field, arguments).function().value - } - - public override fun derivativeOrNull(symbol: Symbol): Expression = Expression { arguments -> - //val bindings = arguments.entries.map { it.key.bind(it.value) } - val derivationResult = SimpleAutoDiffField(field, arguments).differentiate(function) - derivationResult.derivative(symbol) - } -} - -/** - * Generate [AutoDiffProcessor] for [SimpleAutoDiffExpression] - */ -public fun > simpleAutoDiff(field: F): AutoDiffProcessor, SimpleAutoDiffField, Expression> = - AutoDiffProcessor { function -> - SimpleAutoDiffExpression(field, function) - } - -// Extensions for differentiation of various basic mathematical functions - -// x ^ 2 -public fun > SimpleAutoDiffField.sqr(x: AutoDiffValue): AutoDiffValue = - derive(const { x.value * x.value }) { z -> x.d += z.d * 2 * x.value } - -// x ^ 1/2 -public fun > SimpleAutoDiffField.sqrt(x: AutoDiffValue): AutoDiffValue = - derive(const { sqrt(x.value) }) { z -> x.d += z.d * 0.5 / z.value } - -// x ^ y (const) -public fun > SimpleAutoDiffField.pow( - x: AutoDiffValue, - y: Double, -): AutoDiffValue = - derive(const { power(x.value, y) }) { z -> x.d += z.d * y * power(x.value, y - 1) } - -public fun > SimpleAutoDiffField.pow( - x: AutoDiffValue, - y: Int, -): AutoDiffValue = pow(x, y.toDouble()) - -// exp(x) -public fun > SimpleAutoDiffField.exp(x: AutoDiffValue): AutoDiffValue = - derive(const { exp(x.value) }) { z -> x.d += z.d * z.value } - -// ln(x) -public fun > SimpleAutoDiffField.ln(x: AutoDiffValue): AutoDiffValue = - derive(const { ln(x.value) }) { z -> x.d += z.d / x.value } - -// x ^ y (any) -public fun > SimpleAutoDiffField.pow( - x: AutoDiffValue, - y: AutoDiffValue, -): AutoDiffValue = - exp(y * ln(x)) - -// sin(x) -public fun > SimpleAutoDiffField.sin(x: AutoDiffValue): AutoDiffValue = - derive(const { sin(x.value) }) { z -> x.d += z.d * cos(x.value) } - -// cos(x) -public fun > SimpleAutoDiffField.cos(x: AutoDiffValue): AutoDiffValue = - derive(const { cos(x.value) }) { z -> x.d -= z.d * sin(x.value) } - -public fun > SimpleAutoDiffField.tan(x: AutoDiffValue): AutoDiffValue = - derive(const { tan(x.value) }) { z -> - val c = cos(x.value) - x.d += z.d / (c * c) - } - -public fun > SimpleAutoDiffField.asin(x: AutoDiffValue): AutoDiffValue = - derive(const { asin(x.value) }) { z -> x.d += z.d / sqrt(one - x.value * x.value) } - -public fun > SimpleAutoDiffField.acos(x: AutoDiffValue): AutoDiffValue = - derive(const { acos(x.value) }) { z -> x.d -= z.d / sqrt(one - x.value * x.value) } - -public fun > SimpleAutoDiffField.atan(x: AutoDiffValue): AutoDiffValue = - derive(const { atan(x.value) }) { z -> x.d += z.d / (one + x.value * x.value) } - -public fun > SimpleAutoDiffField.sinh(x: AutoDiffValue): AutoDiffValue = - derive(const { sinh(x.value) }) { z -> x.d += z.d * cosh(x.value) } - -public fun > SimpleAutoDiffField.cosh(x: AutoDiffValue): AutoDiffValue = - derive(const { cosh(x.value) }) { z -> x.d += z.d * sinh(x.value) } - -public fun > SimpleAutoDiffField.tanh(x: AutoDiffValue): AutoDiffValue = - derive(const { tanh(x.value) }) { z -> - val c = cosh(x.value) - x.d += z.d / (c * c) - } - -public fun > SimpleAutoDiffField.asinh(x: AutoDiffValue): AutoDiffValue = - derive(const { asinh(x.value) }) { z -> x.d += z.d / sqrt(one + x.value * x.value) } - -public fun > SimpleAutoDiffField.acosh(x: AutoDiffValue): AutoDiffValue = - derive(const { acosh(x.value) }) { z -> x.d += z.d / (sqrt((x.value - one) * (x.value + one))) } - -public fun > SimpleAutoDiffField.atanh(x: AutoDiffValue): AutoDiffValue = - derive(const { atanh(x.value) }) { z -> x.d += z.d / (one - x.value * x.value) } - -public class SimpleAutoDiffExtendedField>( - context: F, - bindings: Map, -) : ExtendedField>, SimpleAutoDiffField(context, bindings) { - // x ^ 2 - public fun sqr(x: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).sqr(x) - - // x ^ 1/2 - public override fun sqrt(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).sqrt(arg) - - // x ^ y (const) - public override fun power(arg: AutoDiffValue, pow: Number): AutoDiffValue = - (this as SimpleAutoDiffField).pow(arg, pow.toDouble()) - - // exp(x) - public override fun exp(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).exp(arg) - - // ln(x) - public override fun ln(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).ln(arg) - - // x ^ y (any) - public fun pow( - x: AutoDiffValue, - y: AutoDiffValue, - ): AutoDiffValue = exp(y * ln(x)) - - // sin(x) - public override fun sin(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).sin(arg) - - // cos(x) - public override fun cos(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).cos(arg) - - public override fun tan(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).tan(arg) - - public override fun asin(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).asin(arg) - - public override fun acos(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).acos(arg) - - public override fun atan(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).atan(arg) - - public override fun sinh(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).sinh(arg) - - public override fun cosh(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).cosh(arg) - - public override fun tanh(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).tanh(arg) - - public override fun asinh(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).asinh(arg) - - public override fun acosh(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).acosh(arg) - - public override fun atanh(arg: AutoDiffValue): AutoDiffValue = - (this as SimpleAutoDiffField).atanh(arg) -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SymbolIndexer.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SymbolIndexer.kt deleted file mode 100644 index 6c61c7c7d..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/SymbolIndexer.kt +++ /dev/null @@ -1,61 +0,0 @@ -package kscience.kmath.expressions - -import kscience.kmath.linear.Point -import kscience.kmath.structures.BufferFactory -import kscience.kmath.structures.Structure2D - -/** - * An environment to easy transform indexed variables to symbols and back. - * TODO requires multi-receivers to be beutiful - */ -public interface SymbolIndexer { - public val symbols: List - public fun indexOf(symbol: Symbol): Int = symbols.indexOf(symbol) - - public operator fun List.get(symbol: Symbol): T { - require(size == symbols.size) { "The input list size for indexer should be ${symbols.size} but $size found" } - return get(this@SymbolIndexer.indexOf(symbol)) - } - - public operator fun Array.get(symbol: Symbol): T { - require(size == symbols.size) { "The input array size for indexer should be ${symbols.size} but $size found" } - return get(this@SymbolIndexer.indexOf(symbol)) - } - - public operator fun DoubleArray.get(symbol: Symbol): Double { - require(size == symbols.size) { "The input array size for indexer should be ${symbols.size} but $size found" } - return get(this@SymbolIndexer.indexOf(symbol)) - } - - public operator fun Point.get(symbol: Symbol): T { - require(size == symbols.size) { "The input buffer size for indexer should be ${symbols.size} but $size found" } - return get(this@SymbolIndexer.indexOf(symbol)) - } - - public fun DoubleArray.toMap(): Map { - require(size == symbols.size) { "The input array size for indexer should be ${symbols.size} but $size found" } - return symbols.indices.associate { symbols[it] to get(it) } - } - - public operator fun Structure2D.get(rowSymbol: Symbol, columnSymbol: Symbol): T = - get(indexOf(rowSymbol), indexOf(columnSymbol)) - - - public fun Map.toList(): List = symbols.map { getValue(it) } - - public fun Map.toPoint(bufferFactory: BufferFactory): Point = - bufferFactory(symbols.size) { getValue(symbols[it]) } - - public fun Map.toDoubleArray(): DoubleArray = DoubleArray(symbols.size) { getValue(symbols[it]) } -} - -public inline class SimpleSymbolIndexer(override val symbols: List) : SymbolIndexer - -/** - * Execute the block with symbol indexer based on given symbol order - */ -public inline fun withSymbols(vararg symbols: Symbol, block: SymbolIndexer.() -> R): R = - with(SimpleSymbolIndexer(symbols.toList()), block) - -public inline fun withSymbols(symbols: Collection, block: SymbolIndexer.() -> R): R = - with(SimpleSymbolIndexer(symbols.toList()), block) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/expressionBuilders.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/expressionBuilders.kt deleted file mode 100644 index 1603bc21d..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/expressionBuilders.kt +++ /dev/null @@ -1,41 +0,0 @@ -package kscience.kmath.expressions - -import kscience.kmath.operations.ExtendedField -import kscience.kmath.operations.Field -import kscience.kmath.operations.Ring -import kscience.kmath.operations.Space -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - - -/** - * Creates a functional expression with this [Space]. - */ -public inline fun Space.spaceExpression(block: FunctionalExpressionSpace>.() -> Expression): Expression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return FunctionalExpressionSpace(this).block() -} - -/** - * Creates a functional expression with this [Ring]. - */ -public inline fun Ring.ringExpression(block: FunctionalExpressionRing>.() -> Expression): Expression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return FunctionalExpressionRing(this).block() -} - -/** - * Creates a functional expression with this [Field]. - */ -public inline fun Field.fieldExpression(block: FunctionalExpressionField>.() -> Expression): Expression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return FunctionalExpressionField(this).block() -} - -/** - * Creates a functional expression with this [ExtendedField]. - */ -public inline fun ExtendedField.extendedFieldExpression(block: FunctionalExpressionExtendedField>.() -> Expression): Expression { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return FunctionalExpressionExtendedField(this).block() -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt deleted file mode 100644 index 8b50bbe33..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt +++ /dev/null @@ -1,88 +0,0 @@ -package kscience.kmath.linear - -import kscience.kmath.operations.RealField -import kscience.kmath.operations.Ring -import kscience.kmath.structures.* - -/** - * Basic implementation of Matrix space based on [NDStructure] - */ -public class BufferMatrixContext>( - public override val elementContext: R, - private val bufferFactory: BufferFactory, -) : GenericMatrixContext> { - public override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): BufferMatrix { - val buffer = bufferFactory(rows * columns) { offset -> initializer(offset / columns, offset % columns) } - return BufferMatrix(rows, columns, buffer) - } - - public override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) - - public companion object -} - -@Suppress("OVERRIDE_BY_INLINE") -public object RealMatrixContext : GenericMatrixContext> { - public override val elementContext: RealField - get() = RealField - - public override inline fun produce( - rows: Int, - columns: Int, - initializer: (i: Int, j: Int) -> Double, - ): BufferMatrix { - val buffer = RealBuffer(rows * columns) { offset -> initializer(offset / columns, offset % columns) } - return BufferMatrix(rows, columns, buffer) - } - - public override inline fun point(size: Int, initializer: (Int) -> Double): Point = - RealBuffer(size, initializer) -} - -public class BufferMatrix( - public override val rowNum: Int, - public override val colNum: Int, - public val buffer: Buffer, - public override val features: Set = emptySet(), -) : FeaturedMatrix { - - init { - require(buffer.size == rowNum * colNum) { "Dimension mismatch for matrix structure" } - } - - override val shape: IntArray get() = intArrayOf(rowNum, colNum) - - public override fun suggestFeature(vararg features: MatrixFeature): BufferMatrix = - BufferMatrix(rowNum, colNum, buffer, this.features + features) - - public override operator fun get(index: IntArray): T = get(index[0], index[1]) - public override operator fun get(i: Int, j: Int): T = buffer[i * colNum + j] - - public override fun elements(): Sequence> = sequence { - for (i in 0 until rowNum) for (j in 0 until colNum) yield(intArrayOf(i, j) to get(i, j)) - } - - public override fun equals(other: Any?): Boolean { - if (this === other) return true - - return when (other) { - is NDStructure<*> -> return NDStructure.equals(this, other) - else -> false - } - } - - public override fun hashCode(): Int { - var result = buffer.hashCode() - result = 31 * result + features.hashCode() - return result - } - - public override fun toString(): String { - return if (rowNum <= 5 && colNum <= 5) - "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)\n" + - rows.asSequence().joinToString(prefix = "(", postfix = ")", separator = "\n ") { buffer -> - buffer.asSequence().joinToString(separator = "\t") { it.toString() } - } - else "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)" - } -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/FeaturedMatrix.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/FeaturedMatrix.kt deleted file mode 100644 index 68272203c..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/FeaturedMatrix.kt +++ /dev/null @@ -1,83 +0,0 @@ -package kscience.kmath.linear - -import kscience.kmath.operations.Ring -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.Structure2D -import kscience.kmath.structures.asBuffer -import kotlin.math.sqrt - -/** - * A 2d structure plus optional matrix-specific features - */ -public interface FeaturedMatrix : Matrix { - override val shape: IntArray get() = intArrayOf(rowNum, colNum) - public val features: Set - - /** - * Suggest new feature for this matrix. The result is the new matrix that may or may not reuse existing data structure. - * - * The implementation does not guarantee to check that matrix actually have the feature, so one should be careful to - * add only those features that are valid. - */ - public fun suggestFeature(vararg features: MatrixFeature): FeaturedMatrix - - public companion object -} - -public inline fun Structure2D.Companion.real( - rows: Int, - columns: Int, - initializer: (Int, Int) -> Double, -): BufferMatrix = MatrixContext.real.produce(rows, columns, initializer) - -/** - * Build a square matrix from given elements. - */ -public fun Structure2D.Companion.square(vararg elements: T): FeaturedMatrix { - val size: Int = sqrt(elements.size.toDouble()).toInt() - require(size * size == elements.size) { "The number of elements ${elements.size} is not a full square" } - val buffer = elements.asBuffer() - return BufferMatrix(size, size, buffer) -} - -public val Matrix<*>.features: Set get() = (this as? FeaturedMatrix)?.features ?: emptySet() - -/** - * Check if matrix has the given feature class - */ -public inline fun Matrix<*>.hasFeature(): Boolean = - features.find { it is T } != null - -/** - * Get the first feature matching given class. Does not guarantee that matrix has only one feature matching the criteria - */ -public inline fun Matrix<*>.getFeature(): T? = - features.filterIsInstance().firstOrNull() - -/** - * Diagonal matrix of ones. The matrix is virtual no actual matrix is created - */ -public fun > GenericMatrixContext.one(rows: Int, columns: Int): FeaturedMatrix = - VirtualMatrix(rows, columns, DiagonalFeature) { i, j -> - if (i == j) elementContext.one else elementContext.zero - } - - -/** - * A virtual matrix of zeroes - */ -public fun > GenericMatrixContext.zero(rows: Int, columns: Int): FeaturedMatrix = - VirtualMatrix(rows, columns) { _, _ -> elementContext.zero } - -public class TransposedFeature(public val original: Matrix) : MatrixFeature - -/** - * Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A` - */ -public fun Matrix.transpose(): Matrix { - return getFeature>()?.original ?: VirtualMatrix( - colNum, - rowNum, - setOf(TransposedFeature(this)) - ) { i, j -> get(j, i) } -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LinearAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LinearAlgebra.kt deleted file mode 100644 index 034decc2f..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LinearAlgebra.kt +++ /dev/null @@ -1,27 +0,0 @@ -package kscience.kmath.linear - -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.VirtualBuffer - -public typealias Point = Buffer - -/** - * A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors - */ -public interface LinearSolver { - public fun solve(a: Matrix, b: Matrix): Matrix - public fun solve(a: Matrix, b: Point): Point = solve(a, b.asMatrix()).asPoint() - public fun inverse(a: Matrix): Matrix -} - -/** - * Convert matrix to vector if it is possible - */ -public fun Matrix.asPoint(): Point = - if (this.colNum == 1) - VirtualBuffer(rowNum) { get(it, 0) } - else - error("Can't convert matrix with more than one column to vector") - -public fun Point.asMatrix(): VirtualMatrix = VirtualMatrix(size, 1) { i, _ -> get(i) } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixBuilder.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixBuilder.kt deleted file mode 100644 index 91c1ec824..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixBuilder.kt +++ /dev/null @@ -1,46 +0,0 @@ -package kscience.kmath.linear - -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.BufferFactory -import kscience.kmath.structures.Structure2D -import kscience.kmath.structures.asBuffer - -public class MatrixBuilder(public val rows: Int, public val columns: Int) { - public operator fun invoke(vararg elements: T): FeaturedMatrix { - require(rows * columns == elements.size) { "The number of elements ${elements.size} is not equal $rows * $columns" } - val buffer = elements.asBuffer() - return BufferMatrix(rows, columns, buffer) - } - - //TODO add specific matrix builder functions like diagonal, etc -} - -public fun Structure2D.Companion.build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns) - -public fun Structure2D.Companion.row(vararg values: T): FeaturedMatrix { - val buffer = values.asBuffer() - return BufferMatrix(1, values.size, buffer) -} - -public inline fun Structure2D.Companion.row( - size: Int, - factory: BufferFactory = Buffer.Companion::auto, - noinline builder: (Int) -> T -): FeaturedMatrix { - val buffer = factory(size, builder) - return BufferMatrix(1, size, buffer) -} - -public fun Structure2D.Companion.column(vararg values: T): FeaturedMatrix { - val buffer = values.asBuffer() - return BufferMatrix(values.size, 1, buffer) -} - -public inline fun Structure2D.Companion.column( - size: Int, - factory: BufferFactory = Buffer.Companion::auto, - noinline builder: (Int) -> T -): FeaturedMatrix { - val buffer = factory(size, builder) - return BufferMatrix(size, 1, buffer) -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixContext.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixContext.kt deleted file mode 100644 index d9dc57b0f..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixContext.kt +++ /dev/null @@ -1,143 +0,0 @@ -package kscience.kmath.linear - -import kscience.kmath.operations.Ring -import kscience.kmath.operations.SpaceOperations -import kscience.kmath.operations.invoke -import kscience.kmath.operations.sum -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.BufferFactory -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.asSequence - -/** - * Basic operations on matrices. Operates on [Matrix] - */ -public interface MatrixContext> : SpaceOperations> { - /** - * Produce a matrix with this context and given dimensions - */ - public fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): M - - @Suppress("UNCHECKED_CAST") - public override fun binaryOperation(operation: String, left: Matrix, right: Matrix): M = when (operation) { - "dot" -> left dot right - else -> super.binaryOperation(operation, left, right) as M - } - - /** - * Computes the dot product of this matrix and another one. - * - * @receiver the multiplicand. - * @param other the multiplier. - * @return the dot product. - */ - public infix fun Matrix.dot(other: Matrix): M - - /** - * Computes the dot product of this matrix and a vector. - * - * @receiver the multiplicand. - * @param vector the multiplier. - * @return the dot product. - */ - public infix fun Matrix.dot(vector: Point): Point - - /** - * Multiplies a matrix by its element. - * - * @receiver the multiplicand. - * @param value the multiplier. - * @receiver the product. - */ - public operator fun Matrix.times(value: T): M - - /** - * Multiplies an element by a matrix of it. - * - * @receiver the multiplicand. - * @param value the multiplier. - * @receiver the product. - */ - public operator fun T.times(m: Matrix): M = m * this - - public companion object { - /** - * Non-boxing double matrix - */ - public val real: RealMatrixContext = RealMatrixContext - - /** - * A structured matrix with custom buffer - */ - public fun > buffered( - ring: R, - bufferFactory: BufferFactory = Buffer.Companion::boxing, - ): GenericMatrixContext> = BufferMatrixContext(ring, bufferFactory) - - /** - * Automatic buffered matrix, unboxed if it is possible - */ - public inline fun > auto(ring: R): GenericMatrixContext> = - buffered(ring, Buffer.Companion::auto) - } -} - -public interface GenericMatrixContext, out M : Matrix> : MatrixContext { - /** - * The ring context for matrix elements - */ - public val elementContext: R - - /** - * Produce a point compatible with matrix space - */ - public fun point(size: Int, initializer: (Int) -> T): Point - - public override infix fun Matrix.dot(other: Matrix): M { - //TODO add typed error - require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" } - - return produce(rowNum, other.colNum) { i, j -> - val row = rows[i] - val column = other.columns[j] - elementContext { sum(row.asSequence().zip(column.asSequence(), ::multiply)) } - } - } - - public override infix fun Matrix.dot(vector: Point): Point { - //TODO add typed error - require(colNum == vector.size) { "Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})" } - - return point(rowNum) { i -> - val row = rows[i] - elementContext { sum(row.asSequence().zip(vector.asSequence(), ::multiply)) } - } - } - - public override operator fun Matrix.unaryMinus(): M = - produce(rowNum, colNum) { i, j -> elementContext { -get(i, j) } } - - public override fun add(a: Matrix, b: Matrix): M { - require(a.rowNum == b.rowNum && a.colNum == b.colNum) { - "Matrix operation dimension mismatch. [${a.rowNum},${a.colNum}] + [${b.rowNum},${b.colNum}]" - } - - return produce(a.rowNum, a.colNum) { i, j -> elementContext { a[i, j] + b[i, j] } } - } - - public override operator fun Matrix.minus(b: Matrix): M { - require(rowNum == b.rowNum && colNum == b.colNum) { - "Matrix operation dimension mismatch. [$rowNum,$colNum] - [${b.rowNum},${b.colNum}]" - } - - return produce(rowNum, colNum) { i, j -> elementContext { get(i, j) + b[i, j] } } - } - - public override fun multiply(a: Matrix, k: Number): M = - produce(a.rowNum, a.colNum) { i, j -> elementContext { a[i, j] * k } } - - public operator fun Number.times(matrix: FeaturedMatrix): M = multiply(matrix, this) - - public override operator fun Matrix.times(value: T): M = - produce(rowNum, colNum) { i, j -> elementContext { get(i, j) * value } } -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixFeatures.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixFeatures.kt deleted file mode 100644 index a82032e50..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixFeatures.kt +++ /dev/null @@ -1,62 +0,0 @@ -package kscience.kmath.linear - -/** - * A marker interface representing some matrix feature like diagonal, sparse, zero, etc. Features used to optimize matrix - * operations performance in some cases. - */ -public interface MatrixFeature - -/** - * The matrix with this feature is considered to have only diagonal non-null elements - */ -public object DiagonalFeature : MatrixFeature - -/** - * Matrix with this feature has all zero elements - */ -public object ZeroFeature : MatrixFeature - -/** - * Matrix with this feature have unit elements on diagonal and zero elements in all other places - */ -public object UnitFeature : MatrixFeature - -/** - * Inverted matrix feature - */ -public interface InverseMatrixFeature : MatrixFeature { - public val inverse: FeaturedMatrix -} - -/** - * A determinant container - */ -public interface DeterminantFeature : MatrixFeature { - public val determinant: T -} - -@Suppress("FunctionName") -public fun DeterminantFeature(determinant: T): DeterminantFeature = object : DeterminantFeature { - override val determinant: T = determinant -} - -/** - * Lower triangular matrix - */ -public object LFeature : MatrixFeature - -/** - * Upper triangular feature - */ -public object UFeature : MatrixFeature - -/** - * TODO add documentation - */ -public interface LUPDecompositionFeature : MatrixFeature { - public val l: FeaturedMatrix - public val u: FeaturedMatrix - public val p: FeaturedMatrix -} - -//TODO add sparse matrix feature diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VectorSpace.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VectorSpace.kt deleted file mode 100644 index 2a3b8f5d1..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VectorSpace.kt +++ /dev/null @@ -1,70 +0,0 @@ -package kscience.kmath.linear - -import kscience.kmath.operations.RealField -import kscience.kmath.operations.Space -import kscience.kmath.operations.invoke -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.BufferFactory - -/** - * A linear space for vectors. - * Could be used on any point-like structure - */ -public interface VectorSpace> : Space> { - public val size: Int - public val space: S - override val zero: Point get() = produce { space.zero } - - public fun produce(initializer: S.(Int) -> T): Point - - /** - * Produce a space-element of this vector space for expressions - */ - //fun produceElement(initializer: (Int) -> T): Vector - - override fun add(a: Point, b: Point): Point = produce { space { a[it] + b[it] } } - - override fun multiply(a: Point, k: Number): Point = produce { space { a[it] * k } } - - //TODO add basis - - public companion object { - private val realSpaceCache: MutableMap> = hashMapOf() - - /** - * Non-boxing double vector space - */ - public fun real(size: Int): BufferVectorSpace = realSpaceCache.getOrPut(size) { - BufferVectorSpace( - size, - RealField, - Buffer.Companion::auto - ) - } - - /** - * A structured vector space with custom buffer - */ - public fun > buffered( - size: Int, - space: S, - bufferFactory: BufferFactory = Buffer.Companion::boxing, - ): BufferVectorSpace = BufferVectorSpace(size, space, bufferFactory) - - /** - * Automatic buffered vector, unboxed if it is possible - */ - public inline fun > auto(size: Int, space: S): VectorSpace = - buffered(size, space, Buffer.Companion::auto) - } -} - - -public class BufferVectorSpace>( - override val size: Int, - override val space: S, - public val bufferFactory: BufferFactory, -) : VectorSpace { - override fun produce(initializer: S.(Int) -> T): Buffer = bufferFactory(size) { space.initializer(it) } - //override fun produceElement(initializer: (Int) -> T): Vector = BufferVector(this, produce(initializer)) -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/annotations.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/annotations.kt deleted file mode 100644 index d70ac7b39..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/annotations.kt +++ /dev/null @@ -1,4 +0,0 @@ -package kscience.kmath.misc - -@RequiresOptIn("This API is unstable and could change in future", RequiresOptIn.Level.WARNING) -public annotation class UnstableKMathAPI \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/cumulative.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/cumulative.kt deleted file mode 100644 index 72d2f2388..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/cumulative.kt +++ /dev/null @@ -1,74 +0,0 @@ -package kscience.kmath.misc - -import kscience.kmath.operations.Space -import kscience.kmath.operations.invoke -import kotlin.jvm.JvmName - -/** - * Generic cumulative operation on iterator. - * - * @param T the type of initial iterable. - * @param R the type of resulting iterable. - * @param initial lazy evaluated. - */ -public inline fun Iterator.cumulative(initial: R, crossinline operation: (R, T) -> R): Iterator = - object : Iterator { - var state: R = initial - - override fun hasNext(): Boolean = this@cumulative.hasNext() - - override fun next(): R { - state = operation(state, this@cumulative.next()) - return state - } - } - -public inline fun Iterable.cumulative(initial: R, crossinline operation: (R, T) -> R): Iterable = - Iterable { this@cumulative.iterator().cumulative(initial, operation) } - -public inline fun Sequence.cumulative(initial: R, crossinline operation: (R, T) -> R): Sequence = - Sequence { this@cumulative.iterator().cumulative(initial, operation) } - -public fun List.cumulative(initial: R, operation: (R, T) -> R): List = - iterator().cumulative(initial, operation).asSequence().toList() - -//Cumulative sum - -/** - * Cumulative sum with custom space - */ -public fun Iterable.cumulativeSum(space: Space): Iterable = - space { cumulative(zero) { element: T, sum: T -> sum + element } } - -@JvmName("cumulativeSumOfDouble") -public fun Iterable.cumulativeSum(): Iterable = cumulative(0.0) { element, sum -> sum + element } - -@JvmName("cumulativeSumOfInt") -public fun Iterable.cumulativeSum(): Iterable = cumulative(0) { element, sum -> sum + element } - -@JvmName("cumulativeSumOfLong") -public fun Iterable.cumulativeSum(): Iterable = cumulative(0L) { element, sum -> sum + element } - -public fun Sequence.cumulativeSum(space: Space): Sequence = - space { cumulative(zero) { element: T, sum: T -> sum + element } } - -@JvmName("cumulativeSumOfDouble") -public fun Sequence.cumulativeSum(): Sequence = cumulative(0.0) { element, sum -> sum + element } - -@JvmName("cumulativeSumOfInt") -public fun Sequence.cumulativeSum(): Sequence = cumulative(0) { element, sum -> sum + element } - -@JvmName("cumulativeSumOfLong") -public fun Sequence.cumulativeSum(): Sequence = cumulative(0L) { element, sum -> sum + element } - -public fun List.cumulativeSum(space: Space): List = - space { cumulative(zero) { element: T, sum: T -> sum + element } } - -@JvmName("cumulativeSumOfDouble") -public fun List.cumulativeSum(): List = cumulative(0.0) { element, sum -> sum + element } - -@JvmName("cumulativeSumOfInt") -public fun List.cumulativeSum(): List = cumulative(0) { element, sum -> sum + element } - -@JvmName("cumulativeSumOfLong") -public fun List.cumulativeSum(): List = cumulative(0L) { element, sum -> sum + element } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/NumberAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/NumberAlgebra.kt deleted file mode 100644 index a2b33a0c4..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/NumberAlgebra.kt +++ /dev/null @@ -1,266 +0,0 @@ -package kscience.kmath.operations - -import kotlin.math.abs -import kotlin.math.pow as kpow - -/** - * Advanced Number-like semifield that implements basic operations. - */ -public interface ExtendedFieldOperations : - FieldOperations, - TrigonometricOperations, - HyperbolicOperations, - PowerOperations, - ExponentialOperations { - public override fun tan(arg: T): T = sin(arg) / cos(arg) - public override fun tanh(arg: T): T = sinh(arg) / cosh(arg) - - public override fun unaryOperation(operation: String, arg: T): T = when (operation) { - TrigonometricOperations.COS_OPERATION -> cos(arg) - TrigonometricOperations.SIN_OPERATION -> sin(arg) - TrigonometricOperations.TAN_OPERATION -> tan(arg) - TrigonometricOperations.ACOS_OPERATION -> acos(arg) - TrigonometricOperations.ASIN_OPERATION -> asin(arg) - TrigonometricOperations.ATAN_OPERATION -> atan(arg) - HyperbolicOperations.COSH_OPERATION -> cosh(arg) - HyperbolicOperations.SINH_OPERATION -> sinh(arg) - HyperbolicOperations.TANH_OPERATION -> tanh(arg) - HyperbolicOperations.ACOSH_OPERATION -> acosh(arg) - HyperbolicOperations.ASINH_OPERATION -> asinh(arg) - HyperbolicOperations.ATANH_OPERATION -> atanh(arg) - PowerOperations.SQRT_OPERATION -> sqrt(arg) - ExponentialOperations.EXP_OPERATION -> exp(arg) - ExponentialOperations.LN_OPERATION -> ln(arg) - else -> super.unaryOperation(operation, arg) - } -} - -/** - * Advanced Number-like field that implements basic operations. - */ -public interface ExtendedField : ExtendedFieldOperations, Field { - public override fun sinh(arg: T): T = (exp(arg) - exp(-arg)) / 2 - public override fun cosh(arg: T): T = (exp(arg) + exp(-arg)) / 2 - public override fun tanh(arg: T): T = (exp(arg) - exp(-arg)) / (exp(-arg) + exp(arg)) - public override fun asinh(arg: T): T = ln(sqrt(arg * arg + one) + arg) - public override fun acosh(arg: T): T = ln(arg + sqrt((arg - one) * (arg + one))) - public override fun atanh(arg: T): T = (ln(arg + one) - ln(one - arg)) / 2 - - public override fun rightSideNumberOperation(operation: String, left: T, right: Number): T = when (operation) { - PowerOperations.POW_OPERATION -> power(left, right) - else -> super.rightSideNumberOperation(operation, left, right) - } -} - -/** - * Real field element wrapping double. - * - * @property value the [Double] value wrapped by this [Real]. - * - * TODO inline does not work due to compiler bug. Waiting for fix for KT-27586 - */ -public inline class Real(public val value: Double) : FieldElement { - public override val context: RealField - get() = RealField - - public override fun unwrap(): Double = value - public override fun Double.wrap(): Real = Real(value) - - public companion object -} - -/** - * A field for [Double] without boxing. Does not produce appropriate field element. - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public object RealField : ExtendedField, Norm { - public override val zero: Double - get() = 0.0 - - public override val one: Double - get() = 1.0 - - public override fun binaryOperation(operation: String, left: Double, right: Double): Double = when (operation) { - PowerOperations.POW_OPERATION -> left pow right - else -> super.binaryOperation(operation, left, right) - } - - public override inline fun add(a: Double, b: Double): Double = a + b - public override inline fun multiply(a: Double, k: Number): Double = a * k.toDouble() - - public override inline fun multiply(a: Double, b: Double): Double = a * b - - public override inline fun divide(a: Double, b: Double): Double = a / b - - public override inline fun sin(arg: Double): Double = kotlin.math.sin(arg) - public override inline fun cos(arg: Double): Double = kotlin.math.cos(arg) - public override inline fun tan(arg: Double): Double = kotlin.math.tan(arg) - public override inline fun acos(arg: Double): Double = kotlin.math.acos(arg) - public override inline fun asin(arg: Double): Double = kotlin.math.asin(arg) - public override inline fun atan(arg: Double): Double = kotlin.math.atan(arg) - - public override inline fun sinh(arg: Double): Double = kotlin.math.sinh(arg) - public override inline fun cosh(arg: Double): Double = kotlin.math.cosh(arg) - public override inline fun tanh(arg: Double): Double = kotlin.math.tanh(arg) - public override inline fun asinh(arg: Double): Double = kotlin.math.asinh(arg) - public override inline fun acosh(arg: Double): Double = kotlin.math.acosh(arg) - public override inline fun atanh(arg: Double): Double = kotlin.math.atanh(arg) - - public override inline fun power(arg: Double, pow: Number): Double = arg.kpow(pow.toDouble()) - public override inline fun exp(arg: Double): Double = kotlin.math.exp(arg) - public override inline fun ln(arg: Double): Double = kotlin.math.ln(arg) - - public override inline fun norm(arg: Double): Double = abs(arg) - - public override inline fun Double.unaryMinus(): Double = -this - public override inline fun Double.plus(b: Double): Double = this + b - public override inline fun Double.minus(b: Double): Double = this - b - public override inline fun Double.times(b: Double): Double = this * b - public override inline fun Double.div(b: Double): Double = this / b -} - -/** - * A field for [Float] without boxing. Does not produce appropriate field element. - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public object FloatField : ExtendedField, Norm { - public override val zero: Float - get() = 0.0f - - public override val one: Float - get() = 1.0f - - public override fun binaryOperation(operation: String, left: Float, right: Float): Float = when (operation) { - PowerOperations.POW_OPERATION -> left pow right - else -> super.binaryOperation(operation, left, right) - } - - public override inline fun add(a: Float, b: Float): Float = a + b - public override inline fun multiply(a: Float, k: Number): Float = a * k.toFloat() - - public override inline fun multiply(a: Float, b: Float): Float = a * b - - public override inline fun divide(a: Float, b: Float): Float = a / b - - public override inline fun sin(arg: Float): Float = kotlin.math.sin(arg) - public override inline fun cos(arg: Float): Float = kotlin.math.cos(arg) - public override inline fun tan(arg: Float): Float = kotlin.math.tan(arg) - public override inline fun acos(arg: Float): Float = kotlin.math.acos(arg) - public override inline fun asin(arg: Float): Float = kotlin.math.asin(arg) - public override inline fun atan(arg: Float): Float = kotlin.math.atan(arg) - - public override inline fun sinh(arg: Float): Float = kotlin.math.sinh(arg) - public override inline fun cosh(arg: Float): Float = kotlin.math.cosh(arg) - public override inline fun tanh(arg: Float): Float = kotlin.math.tanh(arg) - public override inline fun asinh(arg: Float): Float = kotlin.math.asinh(arg) - public override inline fun acosh(arg: Float): Float = kotlin.math.acosh(arg) - public override inline fun atanh(arg: Float): Float = kotlin.math.atanh(arg) - - public override inline fun power(arg: Float, pow: Number): Float = arg.kpow(pow.toFloat()) - public override inline fun exp(arg: Float): Float = kotlin.math.exp(arg) - public override inline fun ln(arg: Float): Float = kotlin.math.ln(arg) - - public override inline fun norm(arg: Float): Float = abs(arg) - - public override inline fun Float.unaryMinus(): Float = -this - public override inline fun Float.plus(b: Float): Float = this + b - public override inline fun Float.minus(b: Float): Float = this - b - public override inline fun Float.times(b: Float): Float = this * b - public override inline fun Float.div(b: Float): Float = this / b -} - -/** - * A field for [Int] without boxing. Does not produce corresponding ring element. - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public object IntRing : Ring, Norm { - public override val zero: Int - get() = 0 - - public override val one: Int - get() = 1 - - public override inline fun add(a: Int, b: Int): Int = a + b - public override inline fun multiply(a: Int, k: Number): Int = k.toInt() * a - - public override inline fun multiply(a: Int, b: Int): Int = a * b - - public override inline fun norm(arg: Int): Int = abs(arg) - - public override inline fun Int.unaryMinus(): Int = -this - public override inline fun Int.plus(b: Int): Int = this + b - public override inline fun Int.minus(b: Int): Int = this - b - public override inline fun Int.times(b: Int): Int = this * b -} - -/** - * A field for [Short] without boxing. Does not produce appropriate ring element. - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public object ShortRing : Ring, Norm { - public override val zero: Short - get() = 0 - - public override val one: Short - get() = 1 - - public override inline fun add(a: Short, b: Short): Short = (a + b).toShort() - public override inline fun multiply(a: Short, k: Number): Short = (a * k.toShort()).toShort() - - public override inline fun multiply(a: Short, b: Short): Short = (a * b).toShort() - - public override fun norm(arg: Short): Short = if (arg > 0) arg else (-arg).toShort() - - public override inline fun Short.unaryMinus(): Short = (-this).toShort() - public override inline fun Short.plus(b: Short): Short = (this + b).toShort() - public override inline fun Short.minus(b: Short): Short = (this - b).toShort() - public override inline fun Short.times(b: Short): Short = (this * b).toShort() -} - -/** - * A field for [Byte] without boxing. Does not produce appropriate ring element. - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public object ByteRing : Ring, Norm { - public override val zero: Byte - get() = 0 - - public override val one: Byte - get() = 1 - - public override inline fun add(a: Byte, b: Byte): Byte = (a + b).toByte() - public override inline fun multiply(a: Byte, k: Number): Byte = (a * k.toByte()).toByte() - - public override inline fun multiply(a: Byte, b: Byte): Byte = (a * b).toByte() - - public override fun norm(arg: Byte): Byte = if (arg > 0) arg else (-arg).toByte() - - public override inline fun Byte.unaryMinus(): Byte = (-this).toByte() - public override inline fun Byte.plus(b: Byte): Byte = (this + b).toByte() - public override inline fun Byte.minus(b: Byte): Byte = (this - b).toByte() - public override inline fun Byte.times(b: Byte): Byte = (this * b).toByte() -} - -/** - * A field for [Double] without boxing. Does not produce appropriate ring element. - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public object LongRing : Ring, Norm { - public override val zero: Long - get() = 0 - - public override val one: Long - get() = 1 - - public override inline fun add(a: Long, b: Long): Long = a + b - public override inline fun multiply(a: Long, k: Number): Long = a * k.toLong() - - public override inline fun multiply(a: Long, b: Long): Long = a * b - - public override fun norm(arg: Long): Long = abs(arg) - - public override inline fun Long.unaryMinus(): Long = (-this) - public override inline fun Long.plus(b: Long): Long = (this + b) - public override inline fun Long.minus(b: Long): Long = (this - b) - public override inline fun Long.times(b: Long): Long = (this * b) -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/OptionalOperations.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/OptionalOperations.kt deleted file mode 100644 index f31d61ae1..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/OptionalOperations.kt +++ /dev/null @@ -1,309 +0,0 @@ -package kscience.kmath.operations - -/** - * A container for trigonometric operations for specific type. - * - * @param T the type of element of this structure. - */ -public interface TrigonometricOperations : Algebra { - /** - * Computes the sine of [arg]. - */ - public fun sin(arg: T): T - - /** - * Computes the cosine of [arg]. - */ - public fun cos(arg: T): T - - /** - * Computes the tangent of [arg]. - */ - public fun tan(arg: T): T - - /** - * Computes the inverse sine of [arg]. - */ - public fun asin(arg: T): T - - /** - * Computes the inverse cosine of [arg]. - */ - public fun acos(arg: T): T - - /** - * Computes the inverse tangent of [arg]. - */ - public fun atan(arg: T): T - - public companion object { - /** - * The identifier of sine. - */ - public const val SIN_OPERATION: String = "sin" - - /** - * The identifier of cosine. - */ - public const val COS_OPERATION: String = "cos" - - /** - * The identifier of tangent. - */ - public const val TAN_OPERATION: String = "tan" - - /** - * The identifier of inverse sine. - */ - public const val ASIN_OPERATION: String = "asin" - - /** - * The identifier of inverse cosine. - */ - public const val ACOS_OPERATION: String = "acos" - - /** - * The identifier of inverse tangent. - */ - public const val ATAN_OPERATION: String = "atan" - } -} - -/** - * Computes the sine of [arg]. - */ -public fun >> sin(arg: T): T = arg.context.sin(arg) - -/** - * Computes the cosine of [arg]. - */ -public fun >> cos(arg: T): T = arg.context.cos(arg) - -/** - * Computes the tangent of [arg]. - */ -public fun >> tan(arg: T): T = arg.context.tan(arg) - -/** - * Computes the inverse sine of [arg]. - */ -public fun >> asin(arg: T): T = arg.context.asin(arg) - -/** - * Computes the inverse cosine of [arg]. - */ -public fun >> acos(arg: T): T = arg.context.acos(arg) - -/** - * Computes the inverse tangent of [arg]. - */ -public fun >> atan(arg: T): T = arg.context.atan(arg) - -/** - * A container for hyperbolic trigonometric operations for specific type. - * - * @param T the type of element of this structure. - */ -public interface HyperbolicOperations : Algebra { - /** - * Computes the hyperbolic sine of [arg]. - */ - public fun sinh(arg: T): T - - /** - * Computes the hyperbolic cosine of [arg]. - */ - public fun cosh(arg: T): T - - /** - * Computes the hyperbolic tangent of [arg]. - */ - public fun tanh(arg: T): T - - /** - * Computes the inverse hyperbolic sine of [arg]. - */ - public fun asinh(arg: T): T - - /** - * Computes the inverse hyperbolic cosine of [arg]. - */ - public fun acosh(arg: T): T - - /** - * Computes the inverse hyperbolic tangent of [arg]. - */ - public fun atanh(arg: T): T - - public companion object { - /** - * The identifier of hyperbolic sine. - */ - public const val SINH_OPERATION: String = "sinh" - - /** - * The identifier of hyperbolic cosine. - */ - public const val COSH_OPERATION: String = "cosh" - - /** - * The identifier of hyperbolic tangent. - */ - public const val TANH_OPERATION: String = "tanh" - - /** - * The identifier of inverse hyperbolic sine. - */ - public const val ASINH_OPERATION: String = "asinh" - - /** - * The identifier of inverse hyperbolic cosine. - */ - public const val ACOSH_OPERATION: String = "acosh" - - /** - * The identifier of inverse hyperbolic tangent. - */ - public const val ATANH_OPERATION: String = "atanh" - } -} - -/** - * Computes the hyperbolic sine of [arg]. - */ -public fun >> sinh(arg: T): T = arg.context.sinh(arg) - -/** - * Computes the hyperbolic cosine of [arg]. - */ -public fun >> cosh(arg: T): T = arg.context.cosh(arg) - -/** - * Computes the hyperbolic tangent of [arg]. - */ -public fun >> tanh(arg: T): T = arg.context.tanh(arg) - -/** - * Computes the inverse hyperbolic sine of [arg]. - */ -public fun >> asinh(arg: T): T = arg.context.asinh(arg) - -/** - * Computes the inverse hyperbolic cosine of [arg]. - */ -public fun >> acosh(arg: T): T = arg.context.acosh(arg) - -/** - * Computes the inverse hyperbolic tangent of [arg]. - */ -public fun >> atanh(arg: T): T = arg.context.atanh(arg) - -/** - * A context extension to include power operations based on exponentiation. - * - * @param T the type of element of this structure. - */ -public interface PowerOperations : Algebra { - /** - * Raises [arg] to the power [pow]. - */ - public fun power(arg: T, pow: Number): T - - /** - * Computes the square root of the value [arg]. - */ - public fun sqrt(arg: T): T = power(arg, 0.5) - - /** - * Raises this value to the power [pow]. - */ - public infix fun T.pow(pow: Number): T = power(this, pow) - - public companion object { - /** - * The identifier of exponentiation. - */ - public const val POW_OPERATION: String = "pow" - - /** - * The identifier of square root. - */ - public const val SQRT_OPERATION: String = "sqrt" - } -} - -/** - * Raises this element to the power [pow]. - * - * @receiver the base. - * @param power the exponent. - * @return the base raised to the power. - */ -public infix fun >> T.pow(power: Double): T = context.power(this, power) - -/** - * Computes the square root of the value [arg]. - */ -public fun >> sqrt(arg: T): T = arg pow 0.5 - -/** - * Computes the square of the value [arg]. - */ -public fun >> sqr(arg: T): T = arg pow 2.0 - -/** - * A container for operations related to `exp` and `ln` functions. - * - * @param T the type of element of this structure. - */ -public interface ExponentialOperations : Algebra { - /** - * Computes Euler's number `e` raised to the power of the value [arg]. - */ - public fun exp(arg: T): T - - /** - * Computes the natural logarithm (base `e`) of the value [arg]. - */ - public fun ln(arg: T): T - - public companion object { - /** - * The identifier of exponential function. - */ - public const val EXP_OPERATION: String = "exp" - - /** - * The identifier of natural logarithm. - */ - public const val LN_OPERATION: String = "ln" - } -} - -/** - * The identifier of exponential function. - */ -public fun >> exp(arg: T): T = arg.context.exp(arg) - -/** - * The identifier of natural logarithm. - */ -public fun >> ln(arg: T): T = arg.context.ln(arg) - -/** - * A container for norm functional on element. - * - * @param T the type of element having norm defined. - * @param R the type of norm. - */ -public interface Norm { - /** - * Computes the norm of [arg] (i.e. absolute value or vector length). - */ - public fun norm(arg: T): R -} - -/** - * Computes the norm of [arg] (i.e. absolute value or vector length). - */ -public fun >, R> norm(arg: T): R = arg.context.norm(arg) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferAccessor2D.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferAccessor2D.kt deleted file mode 100644 index 5d7ba611f..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferAccessor2D.kt +++ /dev/null @@ -1,48 +0,0 @@ -package kscience.kmath.structures - -/** - * A context that allows to operate on a [MutableBuffer] as on 2d array - */ -internal class BufferAccessor2D( - public val rowNum: Int, - public val colNum: Int, - val factory: MutableBufferFactory, -) { - public operator fun Buffer.get(i: Int, j: Int): T = get(i + colNum * j) - - public operator fun MutableBuffer.set(i: Int, j: Int, value: T) { - set(i + colNum * j, value) - } - - public inline fun create(crossinline init: (i: Int, j: Int) -> T): MutableBuffer = - factory(rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) } - - public fun create(mat: Structure2D): MutableBuffer = create { i, j -> mat[i, j] } - - //TODO optimize wrapper - public fun MutableBuffer.collect(): Structure2D = NDStructure.build( - DefaultStrides(intArrayOf(rowNum, colNum)), - factory - ) { (i, j) -> - get(i, j) - }.as2D() - - public inner class Row(public val buffer: MutableBuffer, public val rowIndex: Int) : MutableBuffer { - override val size: Int get() = colNum - - override operator fun get(index: Int): T = buffer[rowIndex, index] - - override operator fun set(index: Int, value: T) { - buffer[rowIndex, index] = value - } - - override fun copy(): MutableBuffer = factory(colNum) { get(it) } - override operator fun iterator(): Iterator = (0 until colNum).map(::get).iterator() - - } - - /** - * Get row - */ - public fun MutableBuffer.row(i: Int): Row = Row(this, i) -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDAlgebra.kt deleted file mode 100644 index 3dcd0322c..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDAlgebra.kt +++ /dev/null @@ -1,43 +0,0 @@ -package kscience.kmath.structures - -import kscience.kmath.operations.* - -public interface BufferedNDAlgebra : NDAlgebra> { - public val strides: Strides - - public override fun check(vararg elements: NDBuffer): Array> { - require(elements.all { it.strides == strides }) { "Strides mismatch" } - return elements - } - - /** - * Convert any [NDStructure] to buffered structure using strides from this context. - * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over - * indices. - * - * If the argument is [NDBuffer] with different strides structure, the new element will be produced. - */ - public fun NDStructure.toBuffer(): NDBuffer = - if (this is NDBuffer && this.strides == this@BufferedNDAlgebra.strides) - this - else - produce { index -> this@toBuffer[index] } - - /** - * Convert a buffer to element of this algebra - */ - public fun NDBuffer.toElement(): MathElement> -} - - -public interface BufferedNDSpace> : NDSpace>, BufferedNDAlgebra { - public override fun NDBuffer.toElement(): SpaceElement, *, out BufferedNDSpace> -} - -public interface BufferedNDRing> : NDRing>, BufferedNDSpace { - override fun NDBuffer.toElement(): RingElement, *, out BufferedNDRing> -} - -public interface BufferedNDField> : NDField>, BufferedNDRing { - override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt deleted file mode 100644 index bfec6f871..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt +++ /dev/null @@ -1,299 +0,0 @@ -package kscience.kmath.structures - -import kscience.kmath.operations.Complex -import kscience.kmath.operations.complex -import kotlin.reflect.KClass - -/** - * Function that produces [Buffer] from its size and function that supplies values. - * - * @param T the type of buffer. - */ -public typealias BufferFactory = (Int, (Int) -> T) -> Buffer - -/** - * Function that produces [MutableBuffer] from its size and function that supplies values. - * - * @param T the type of buffer. - */ -public typealias MutableBufferFactory = (Int, (Int) -> T) -> MutableBuffer - -/** - * A generic immutable random-access structure for both primitives and objects. - * - * @param T the type of elements contained in the buffer. - */ -public interface Buffer { - /** - * The size of this buffer. - */ - public val size: Int - - /** - * Gets element at given index. - */ - public operator fun get(index: Int): T - - /** - * Iterates over all elements. - */ - public operator fun iterator(): Iterator - - /** - * Checks content equality with another buffer. - */ - public fun contentEquals(other: Buffer<*>): Boolean = - asSequence().mapIndexed { index, value -> value == other[index] }.all { it } - - public companion object { - /** - * Creates a [RealBuffer] with the specified [size], where each element is calculated by calling the specified - * [initializer] function. - */ - public inline fun real(size: Int, initializer: (Int) -> Double): RealBuffer = - RealBuffer(size) { initializer(it) } - - /** - * Creates a [ListBuffer] of given type [T] with given [size]. Each element is calculated by calling the - * specified [initializer] function. - */ - public inline fun boxing(size: Int, initializer: (Int) -> T): Buffer = - ListBuffer(List(size, initializer)) - - // TODO add resolution based on Annotation or companion resolution - - /** - * Creates a [Buffer] of given [type]. If the type is primitive, specialized buffers are used ([IntBuffer], - * [RealBuffer], etc.), [ListBuffer] is returned otherwise. - * - * The [size] is specified, and each element is calculated by calling the specified [initializer] function. - */ - @Suppress("UNCHECKED_CAST") - public inline fun auto(type: KClass, size: Int, initializer: (Int) -> T): Buffer = - when (type) { - Double::class -> RealBuffer(size) { initializer(it) as Double } as Buffer - Short::class -> ShortBuffer(size) { initializer(it) as Short } as Buffer - Int::class -> IntBuffer(size) { initializer(it) as Int } as Buffer - Long::class -> LongBuffer(size) { initializer(it) as Long } as Buffer - Float::class -> FloatBuffer(size) { initializer(it) as Float } as Buffer - Complex::class -> complex(size) { initializer(it) as Complex } as Buffer - else -> boxing(size, initializer) - } - - /** - * Creates a [Buffer] of given type [T]. If the type is primitive, specialized buffers are used ([IntBuffer], - * [RealBuffer], etc.), [ListBuffer] is returned otherwise. - * - * The [size] is specified, and each element is calculated by calling the specified [initializer] function. - */ - @Suppress("UNCHECKED_CAST") - public inline fun auto(size: Int, initializer: (Int) -> T): Buffer = - auto(T::class, size, initializer) - } -} - -/** - * Creates a sequence that returns all elements from this [Buffer]. - */ -public fun Buffer.asSequence(): Sequence = Sequence(::iterator) - -/** - * Creates an iterable that returns all elements from this [Buffer]. - */ -public fun Buffer.asIterable(): Iterable = Iterable(::iterator) - -/** - * Converts this [Buffer] to a new [List] - */ -public fun Buffer.toList(): List = asSequence().toList() - -/** - * Returns an [IntRange] of the valid indices for this [Buffer]. - */ -public val Buffer<*>.indices: IntRange get() = 0 until size - -/** - * A generic mutable random-access structure for both primitives and objects. - * - * @param T the type of elements contained in the buffer. - */ -public interface MutableBuffer : Buffer { - /** - * Sets the array element at the specified [index] to the specified [value]. - */ - public operator fun set(index: Int, value: T) - - /** - * Returns a shallow copy of the buffer. - */ - public fun copy(): MutableBuffer - - public companion object { - /** - * Create a boxing mutable buffer of given type - */ - public inline fun boxing(size: Int, initializer: (Int) -> T): MutableBuffer = - MutableListBuffer(MutableList(size, initializer)) - - /** - * Creates a [MutableBuffer] of given [type]. If the type is primitive, specialized buffers are used - * ([IntBuffer], [RealBuffer], etc.), [ListBuffer] is returned otherwise. - * - * The [size] is specified, and each element is calculated by calling the specified [initializer] function. - */ - @Suppress("UNCHECKED_CAST") - public inline fun auto(type: KClass, size: Int, initializer: (Int) -> T): MutableBuffer = - when (type) { - Double::class -> RealBuffer(size) { initializer(it) as Double } as MutableBuffer - Short::class -> ShortBuffer(size) { initializer(it) as Short } as MutableBuffer - Int::class -> IntBuffer(size) { initializer(it) as Int } as MutableBuffer - Float::class -> FloatBuffer(size) { initializer(it) as Float } as MutableBuffer - Long::class -> LongBuffer(size) { initializer(it) as Long } as MutableBuffer - Complex::class -> complex(size) { initializer(it) as Complex } as MutableBuffer - else -> boxing(size, initializer) - } - - /** - * Creates a [MutableBuffer] of given type [T]. If the type is primitive, specialized buffers are used - * ([IntBuffer], [RealBuffer], etc.), [ListBuffer] is returned otherwise. - * - * The [size] is specified, and each element is calculated by calling the specified [initializer] function. - */ - @Suppress("UNCHECKED_CAST") - public inline fun auto(size: Int, initializer: (Int) -> T): MutableBuffer = - auto(T::class, size, initializer) - - /** - * Creates a [RealBuffer] with the specified [size], where each element is calculated by calling the specified - * [initializer] function. - */ - public inline fun real(size: Int, initializer: (Int) -> Double): RealBuffer = - RealBuffer(size) { initializer(it) } - } -} - -/** - * [Buffer] implementation over [List]. - * - * @param T the type of elements contained in the buffer. - * @property list The underlying list. - */ -public inline class ListBuffer(public val list: List) : Buffer { - override val size: Int - get() = list.size - - override operator fun get(index: Int): T = list[index] - override operator fun iterator(): Iterator = list.iterator() -} - -/** - * Returns an [ListBuffer] that wraps the original list. - */ -public fun List.asBuffer(): ListBuffer = ListBuffer(this) - -/** - * Creates a new [ListBuffer] with the specified [size], where each element is calculated by calling the specified - * [init] function. - * - * The function [init] is called for each array element sequentially starting from the first one. - * It should return the value for an array element given its index. - */ -public inline fun ListBuffer(size: Int, init: (Int) -> T): ListBuffer = List(size, init).asBuffer() - -/** - * [MutableBuffer] implementation over [MutableList]. - * - * @param T the type of elements contained in the buffer. - * @property list The underlying list. - */ -public inline class MutableListBuffer(public val list: MutableList) : MutableBuffer { - override val size: Int - get() = list.size - - override operator fun get(index: Int): T = list[index] - - override operator fun set(index: Int, value: T) { - list[index] = value - } - - override operator fun iterator(): Iterator = list.iterator() - override fun copy(): MutableBuffer = MutableListBuffer(ArrayList(list)) -} - -/** - * [MutableBuffer] implementation over [Array]. - * - * @param T the type of elements contained in the buffer. - * @property array The underlying array. - */ -public class ArrayBuffer(private val array: Array) : MutableBuffer { - // Can't inline because array is invariant - override val size: Int - get() = array.size - - override operator fun get(index: Int): T = array[index] - - override operator fun set(index: Int, value: T) { - array[index] = value - } - - override operator fun iterator(): Iterator = array.iterator() - override fun copy(): MutableBuffer = ArrayBuffer(array.copyOf()) -} - -/** - * Returns an [ArrayBuffer] that wraps the original array. - */ -public fun Array.asBuffer(): ArrayBuffer = ArrayBuffer(this) - -/** - * Immutable wrapper for [MutableBuffer]. - * - * @param T the type of elements contained in the buffer. - * @property buffer The underlying buffer. - */ -public inline class ReadOnlyBuffer(public val buffer: MutableBuffer) : Buffer { - override val size: Int get() = buffer.size - - override operator fun get(index: Int): T = buffer[index] - - override operator fun iterator(): Iterator = buffer.iterator() -} - -/** - * A buffer with content calculated on-demand. The calculated content is not stored, so it is recalculated on each call. - * Useful when one needs single element from the buffer. - * - * @param T the type of elements provided by the buffer. - */ -public class VirtualBuffer(override val size: Int, private val generator: (Int) -> T) : Buffer { - override operator fun get(index: Int): T { - if (index < 0 || index >= size) throw IndexOutOfBoundsException("Expected index from 0 to ${size - 1}, but found $index") - return generator(index) - } - - override operator fun iterator(): Iterator = (0 until size).asSequence().map(generator).iterator() - - override fun contentEquals(other: Buffer<*>): Boolean { - return if (other is VirtualBuffer) { - this.size == other.size && this.generator == other.generator - } else { - super.contentEquals(other) - } - } -} - -/** - * Convert this buffer to read-only buffer. - */ -public fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) ReadOnlyBuffer(this) else this - -/** - * Typealias for buffer transformations. - */ -public typealias BufferTransform = (Buffer) -> Buffer - -/** - * Typealias for buffer transformations with suspend function. - */ -public typealias SuspendBufferTransform = suspend (Buffer) -> Buffer diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/MemoryBuffer.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/MemoryBuffer.kt deleted file mode 100644 index 66c9212cf..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/MemoryBuffer.kt +++ /dev/null @@ -1,61 +0,0 @@ -package kscience.kmath.structures - -import kscience.kmath.memory.* - -/** - * A non-boxing buffer over [Memory] object. - * - * @param T the type of elements contained in the buffer. - * @property memory the underlying memory segment. - * @property spec the spec of [T] type. - */ -public open class MemoryBuffer(protected val memory: Memory, protected val spec: MemorySpec) : Buffer { - override val size: Int get() = memory.size / spec.objectSize - - private val reader: MemoryReader = memory.reader() - - override operator fun get(index: Int): T = reader.read(spec, spec.objectSize * index) - override operator fun iterator(): Iterator = (0 until size).asSequence().map { get(it) }.iterator() - - public companion object { - public fun create(spec: MemorySpec, size: Int): MemoryBuffer = - MemoryBuffer(Memory.allocate(size * spec.objectSize), spec) - - public inline fun create( - spec: MemorySpec, - size: Int, - initializer: (Int) -> T - ): MemoryBuffer = MutableMemoryBuffer(Memory.allocate(size * spec.objectSize), spec).also { buffer -> - (0 until size).forEach { buffer[it] = initializer(it) } - } - } -} - -/** - * A mutable non-boxing buffer over [Memory] object. - * - * @param T the type of elements contained in the buffer. - * @property memory the underlying memory segment. - * @property spec the spec of [T] type. - */ -public class MutableMemoryBuffer(memory: Memory, spec: MemorySpec) : MemoryBuffer(memory, spec), - MutableBuffer { - - private val writer: MemoryWriter = memory.writer() - - override operator fun set(index: Int, value: T): Unit = writer.write(spec, spec.objectSize * index, value) - override fun copy(): MutableBuffer = MutableMemoryBuffer(memory.copy(), spec) - - public companion object { - public fun create(spec: MemorySpec, size: Int): MutableMemoryBuffer = - MutableMemoryBuffer(Memory.allocate(size * spec.objectSize), spec) - - public inline fun create( - spec: MemorySpec, - size: Int, - initializer: (Int) -> T - ): MutableMemoryBuffer = MutableMemoryBuffer(Memory.allocate(size * spec.objectSize), spec).also { buffer -> - (0 until size).forEach { buffer[it] = initializer(it) } - } - } -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDAlgebra.kt deleted file mode 100644 index d7b019c65..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDAlgebra.kt +++ /dev/null @@ -1,259 +0,0 @@ -package kscience.kmath.structures - -import kscience.kmath.operations.Complex -import kscience.kmath.operations.Field -import kscience.kmath.operations.Ring -import kscience.kmath.operations.Space -import kotlin.native.concurrent.ThreadLocal - -/** - * An exception is thrown when the expected ans actual shape of NDArray differs. - * - * @property expected the expected shape. - * @property actual the actual shape. - */ -public class ShapeMismatchException(public val expected: IntArray, public val actual: IntArray) : - RuntimeException("Shape ${actual.contentToString()} doesn't fit in expected shape ${expected.contentToString()}.") - -/** - * The base interface for all ND-algebra implementations. - * - * @param T the type of ND-structure element. - * @param C the type of the element context. - * @param N the type of the structure. - */ -public interface NDAlgebra> { - /** - * The shape of ND-structures this algebra operates on. - */ - public val shape: IntArray - - /** - * The algebra over elements of ND structure. - */ - public val elementContext: C - - /** - * Produces a new [N] structure using given initializer function. - */ - public fun produce(initializer: C.(IntArray) -> T): N - - /** - * Maps elements from one structure to another one by applying [transform] to them. - */ - public fun map(arg: N, transform: C.(T) -> T): N - - /** - * Maps elements from one structure to another one by applying [transform] to them alongside with their indices. - */ - public fun mapIndexed(arg: N, transform: C.(index: IntArray, T) -> T): N - - /** - * Combines two structures into one. - */ - public fun combine(a: N, b: N, transform: C.(T, T) -> T): N - - /** - * Checks if given element is consistent with this context. - * - * @param element the structure to check. - * @return the valid structure. - */ - public fun check(element: N): N { - if (!element.shape.contentEquals(shape)) throw ShapeMismatchException(shape, element.shape) - return element - } - - /** - * Checks if given elements are consistent with this context. - * - * @param elements the structures to check. - * @return the array of valid structures. - */ - public fun check(vararg elements: N): Array = elements - .map(NDStructure::shape) - .singleOrNull { !shape.contentEquals(it) } - ?.let> { throw ShapeMismatchException(shape, it) } - ?: elements - - /** - * Element-wise invocation of function working on [T] on a [NDStructure]. - */ - public operator fun Function1.invoke(structure: N): N = map(structure) { value -> this@invoke(value) } - - public companion object -} - -/** - * Space of [NDStructure]. - * - * @param T the type of the element contained in ND structure. - * @param N the type of ND structure. - * @param S the type of space of structure elements. - */ -public interface NDSpace, N : NDStructure> : Space, NDAlgebra { - /** - * Element-wise addition. - * - * @param a the addend. - * @param b the augend. - * @return the sum. - */ - public override fun add(a: N, b: N): N = combine(a, b) { aValue, bValue -> add(aValue, bValue) } - - /** - * Element-wise multiplication by scalar. - * - * @param a the multiplicand. - * @param k the multiplier. - * @return the product. - */ - public override fun multiply(a: N, k: Number): N = map(a) { multiply(it, k) } - - // TODO move to extensions after KEEP-176 - - /** - * Adds an ND structure to an element of it. - * - * @receiver the addend. - * @param arg the augend. - * @return the sum. - */ - public operator fun N.plus(arg: T): N = map(this) { value -> add(arg, value) } - - /** - * Subtracts an element from ND structure of it. - * - * @receiver the dividend. - * @param arg the divisor. - * @return the quotient. - */ - public operator fun N.minus(arg: T): N = map(this) { value -> add(arg, -value) } - - /** - * Adds an element to ND structure of it. - * - * @receiver the addend. - * @param arg the augend. - * @return the sum. - */ - public operator fun T.plus(arg: N): N = map(arg) { value -> add(this@plus, value) } - - /** - * Subtracts an ND structure from an element of it. - * - * @receiver the dividend. - * @param arg the divisor. - * @return the quotient. - */ - public operator fun T.minus(arg: N): N = map(arg) { value -> add(-this@minus, value) } - - public companion object -} - -/** - * Ring of [NDStructure]. - * - * @param T the type of the element contained in ND structure. - * @param N the type of ND structure. - * @param R the type of ring of structure elements. - */ -public interface NDRing, N : NDStructure> : Ring, NDSpace { - /** - * Element-wise multiplication. - * - * @param a the multiplicand. - * @param b the multiplier. - * @return the product. - */ - public override fun multiply(a: N, b: N): N = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) } - - //TODO move to extensions after KEEP-176 - - /** - * Multiplies an ND structure by an element of it. - * - * @receiver the multiplicand. - * @param arg the multiplier. - * @return the product. - */ - public operator fun N.times(arg: T): N = map(this) { value -> multiply(arg, value) } - - /** - * Multiplies an element by a ND structure of it. - * - * @receiver the multiplicand. - * @param arg the multiplier. - * @return the product. - */ - public operator fun T.times(arg: N): N = map(arg) { value -> multiply(this@times, value) } - - public companion object -} - -/** - * Field of [NDStructure]. - * - * @param T the type of the element contained in ND structure. - * @param N the type of ND structure. - * @param F the type field of structure elements. - */ -public interface NDField, N : NDStructure> : Field, NDRing { - /** - * Element-wise division. - * - * @param a the dividend. - * @param b the divisor. - * @return the quotient. - */ - public override fun divide(a: N, b: N): N = combine(a, b) { aValue, bValue -> divide(aValue, bValue) } - - //TODO move to extensions after KEEP-176 - /** - * Divides an ND structure by an element of it. - * - * @receiver the dividend. - * @param arg the divisor. - * @return the quotient. - */ - public operator fun N.div(arg: T): N = map(this) { value -> divide(arg, value) } - - /** - * Divides an element by an ND structure of it. - * - * @receiver the dividend. - * @param arg the divisor. - * @return the quotient. - */ - public operator fun T.div(arg: N): N = map(arg) { divide(it, this@div) } - - @ThreadLocal - public companion object { - private val realNDFieldCache: MutableMap = hashMapOf() - - /** - * Create a nd-field for [Double] values or pull it from cache if it was created previously. - */ - public fun real(vararg shape: Int): RealNDField = realNDFieldCache.getOrPut(shape) { RealNDField(shape) } - - /** - * Create an ND field with boxing generic buffer. - */ - public fun > boxing( - field: F, - vararg shape: Int, - bufferFactory: BufferFactory = Buffer.Companion::boxing - ): BoxingNDField = BoxingNDField(shape, field, bufferFactory) - - /** - * Create a most suitable implementation for nd-field using reified class. - */ - @Suppress("UNCHECKED_CAST") - public inline fun > auto(field: F, vararg shape: Int): BufferedNDField = - when { - T::class == Double::class -> real(*shape) as BufferedNDField - T::class == Complex::class -> complex(*shape) as BufferedNDField - else -> BoxingNDField(shape, field, Buffer.Companion::auto) - } - } -} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBuffer.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBuffer.kt deleted file mode 100644 index 769c445d6..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBuffer.kt +++ /dev/null @@ -1,54 +0,0 @@ -package kscience.kmath.structures - -/** - * Specialized [MutableBuffer] implementation over [DoubleArray]. - * - * @property array the underlying array. - */ -@Suppress("OVERRIDE_BY_INLINE") -public inline class RealBuffer(public val array: DoubleArray) : MutableBuffer { - override val size: Int get() = array.size - - override inline operator fun get(index: Int): Double = array[index] - - override inline operator fun set(index: Int, value: Double) { - array[index] = value - } - - override operator fun iterator(): DoubleIterator = array.iterator() - - override fun copy(): RealBuffer = RealBuffer(array.copyOf()) -} - -/** - * Creates a new [RealBuffer] with the specified [size], where each element is calculated by calling the specified - * [init] function. - * - * The function [init] is called for each array element sequentially starting from the first one. - * It should return the value for an buffer element given its index. - */ -public inline fun RealBuffer(size: Int, init: (Int) -> Double): RealBuffer = RealBuffer(DoubleArray(size) { init(it) }) - -/** - * Returns a new [RealBuffer] of given elements. - */ -public fun RealBuffer(vararg doubles: Double): RealBuffer = RealBuffer(doubles) - -/** - * Simplified [RealBuffer] to array comparison - */ -public fun RealBuffer.contentEquals(vararg doubles: Double): Boolean = array.contentEquals(doubles) - -/** - * Returns a [DoubleArray] containing all of the elements of this [MutableBuffer]. - */ -public val MutableBuffer.array: DoubleArray - get() = (if (this is RealBuffer) array else DoubleArray(size) { get(it) }) - -/** - * Returns [RealBuffer] over this array. - * - * @receiver the array. - * @return the new buffer. - */ -public fun DoubleArray.asBuffer(): RealBuffer = RealBuffer(this) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure2D.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure2D.kt deleted file mode 100644 index 25fdf3f3d..000000000 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure2D.kt +++ /dev/null @@ -1,50 +0,0 @@ -package kscience.kmath.structures - -/** - * A structure that is guaranteed to be two-dimensional - */ -public interface Structure2D : NDStructure { - public val rowNum: Int get() = shape[0] - public val colNum: Int get() = shape[1] - - public operator fun get(i: Int, j: Int): T - - override operator fun get(index: IntArray): T { - require(index.size == 2) { "Index dimension mismatch. Expected 2 but found ${index.size}" } - return get(index[0], index[1]) - } - - public val rows: Buffer> - get() = VirtualBuffer(rowNum) { i -> VirtualBuffer(colNum) { j -> get(i, j) } } - - public val columns: Buffer> - get() = VirtualBuffer(colNum) { j -> VirtualBuffer(rowNum) { i -> get(i, j) } } - - override fun elements(): Sequence> = sequence { - for (i in (0 until rowNum)) - for (j in (0 until colNum)) yield(intArrayOf(i, j) to get(i, j)) - } - - public companion object -} - -/** - * A 2D wrapper for nd-structure - */ -private inline class Structure2DWrapper(val structure: NDStructure) : Structure2D { - override val shape: IntArray get() = structure.shape - - override operator fun get(i: Int, j: Int): T = structure[i, j] - - override fun elements(): Sequence> = structure.elements() -} - -/** - * Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch - */ -public fun NDStructure.as2D(): Structure2D = if (shape.size == 2) - Structure2DWrapper(this) -else - error("Can't create 2d-structure from ${shape.size}d-structure") - -public typealias Matrix = Structure2D diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/Domain.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/Domain.kt similarity index 54% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/domains/Domain.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/Domain.kt index 5c3cff2c5..341383bfb 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/Domain.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/Domain.kt @@ -1,20 +1,20 @@ -package kscience.kmath.domains +package scientifik.kmath.domains -import kscience.kmath.linear.Point +import scientifik.kmath.linear.Point /** * A simple geometric domain. * * @param T the type of element of this domain. */ -public interface Domain { +interface Domain { /** * Checks if the specified point is contained in this domain. */ - public operator fun contains(point: Point): Boolean + operator fun contains(point: Point): Boolean /** * Number of hyperspace dimensions. */ - public val dimension: Int + val dimension: Int } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/HyperSquareDomain.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/HyperSquareDomain.kt similarity index 54% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/domains/HyperSquareDomain.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/HyperSquareDomain.kt index b45cf6bf5..66798c42f 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/HyperSquareDomain.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/HyperSquareDomain.kt @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package kscience.kmath.domains +package scientifik.kmath.domains -import kscience.kmath.linear.Point -import kscience.kmath.structures.RealBuffer -import kscience.kmath.structures.indices +import scientifik.kmath.linear.Point +import scientifik.kmath.structures.RealBuffer +import scientifik.kmath.structures.indices /** * @@ -25,22 +25,23 @@ import kscience.kmath.structures.indices * * @author Alexander Nozik */ -public class HyperSquareDomain(private val lower: RealBuffer, private val upper: RealBuffer) : RealDomain { - public override val dimension: Int get() = lower.size +class HyperSquareDomain(private val lower: RealBuffer, private val upper: RealBuffer) : RealDomain { - public override operator fun contains(point: Point): Boolean = point.indices.all { i -> + override operator fun contains(point: Point): Boolean = point.indices.all { i -> point[i] in lower[i]..upper[i] } - public override fun getLowerBound(num: Int, point: Point): Double? = lower[num] + override val dimension: Int get() = lower.size - public override fun getLowerBound(num: Int): Double? = lower[num] + override fun getLowerBound(num: Int, point: Point): Double? = lower[num] - public override fun getUpperBound(num: Int, point: Point): Double? = upper[num] + override fun getLowerBound(num: Int): Double? = lower[num] - public override fun getUpperBound(num: Int): Double? = upper[num] + override fun getUpperBound(num: Int, point: Point): Double? = upper[num] - public override fun nearestInDomain(point: Point): Point { + override fun getUpperBound(num: Int): Double? = upper[num] + + override fun nearestInDomain(point: Point): Point { val res = DoubleArray(point.size) { i -> when { point[i] < lower[i] -> lower[i] @@ -52,14 +53,16 @@ public class HyperSquareDomain(private val lower: RealBuffer, private val upper: return RealBuffer(*res) } - public override fun volume(): Double { + override fun volume(): Double { var res = 1.0 - for (i in 0 until dimension) { - if (lower[i].isInfinite() || upper[i].isInfinite()) return Double.POSITIVE_INFINITY - if (upper[i] > lower[i]) res *= upper[i] - lower[i] + if (lower[i].isInfinite() || upper[i].isInfinite()) { + return Double.POSITIVE_INFINITY + } + if (upper[i] > lower[i]) { + res *= upper[i] - lower[i] + } } - return res } } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/RealDomain.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/RealDomain.kt similarity index 71% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/domains/RealDomain.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/RealDomain.kt index 369b093bb..7507ccd59 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/domains/RealDomain.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/RealDomain.kt @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package kscience.kmath.domains +package scientifik.kmath.domains -import kscience.kmath.linear.Point +import scientifik.kmath.linear.Point /** * n-dimensional volume * * @author Alexander Nozik */ -public interface RealDomain : Domain { - public fun nearestInDomain(point: Point): Point +interface RealDomain : Domain { + fun nearestInDomain(point: Point): Point /** * The lower edge for the domain going down from point @@ -31,7 +31,7 @@ public interface RealDomain : Domain { * @param point * @return */ - public fun getLowerBound(num: Int, point: Point): Double? + fun getLowerBound(num: Int, point: Point): Double? /** * The upper edge of the domain going up from point @@ -39,25 +39,25 @@ public interface RealDomain : Domain { * @param point * @return */ - public fun getUpperBound(num: Int, point: Point): Double? + fun getUpperBound(num: Int, point: Point): Double? /** * Global lower edge * @param num * @return */ - public fun getLowerBound(num: Int): Double? + fun getLowerBound(num: Int): Double? /** * Global upper edge * @param num * @return */ - public fun getUpperBound(num: Int): Double? + fun getUpperBound(num: Int): Double? /** * Hyper volume * @return */ - public fun volume(): Double + fun volume(): Double } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/UnconstrainedDomain.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/UnconstrainedDomain.kt new file mode 100644 index 000000000..595a3dbe7 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/UnconstrainedDomain.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2015 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package scientifik.kmath.domains + +import scientifik.kmath.linear.Point + +class UnconstrainedDomain(override val dimension: Int) : RealDomain { + override operator fun contains(point: Point): Boolean = true + + override fun getLowerBound(num: Int, point: Point): Double? = Double.NEGATIVE_INFINITY + + override fun getLowerBound(num: Int): Double? = Double.NEGATIVE_INFINITY + + override fun getUpperBound(num: Int, point: Point): Double? = Double.POSITIVE_INFINITY + + override fun getUpperBound(num: Int): Double? = Double.POSITIVE_INFINITY + + override fun nearestInDomain(point: Point): Point = point + + override fun volume(): Double = Double.POSITIVE_INFINITY +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/UnivariateDomain.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/UnivariateDomain.kt new file mode 100644 index 000000000..280dc7d66 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/domains/UnivariateDomain.kt @@ -0,0 +1,47 @@ +package scientifik.kmath.domains + +import scientifik.kmath.linear.Point +import scientifik.kmath.structures.asBuffer + +inline class UnivariateDomain(val range: ClosedFloatingPointRange) : RealDomain { + operator fun contains(d: Double): Boolean = range.contains(d) + + override operator fun contains(point: Point): Boolean { + require(point.size == 0) + return contains(point[0]) + } + + override fun nearestInDomain(point: Point): Point { + require(point.size == 1) + val value = point[0] + return when { + value in range -> point + value >= range.endInclusive -> doubleArrayOf(range.endInclusive).asBuffer() + else -> doubleArrayOf(range.start).asBuffer() + } + } + + override fun getLowerBound(num: Int, point: Point): Double? { + require(num == 0) + return range.start + } + + override fun getUpperBound(num: Int, point: Point): Double? { + require(num == 0) + return range.endInclusive + } + + override fun getLowerBound(num: Int): Double? { + require(num == 0) + return range.start + } + + override fun getUpperBound(num: Int): Double? { + require(num == 0) + return range.endInclusive + } + + override fun volume(): Double = range.endInclusive - range.start + + override val dimension: Int get() = 1 +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Builders.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Builders.kt new file mode 100644 index 000000000..8cd6e28f8 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Builders.kt @@ -0,0 +1,31 @@ +package scientifik.kmath.expressions + +import scientifik.kmath.operations.ExtendedField +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.Space + +/** + * Creates a functional expression with this [Space]. + */ +fun Space.spaceExpression(block: FunctionalExpressionSpace>.() -> Expression): Expression = + FunctionalExpressionSpace(this).run(block) + +/** + * Creates a functional expression with this [Ring]. + */ +fun Ring.ringExpression(block: FunctionalExpressionRing>.() -> Expression): Expression = + FunctionalExpressionRing(this).run(block) + +/** + * Creates a functional expression with this [Field]. + */ +fun Field.fieldExpression(block: FunctionalExpressionField>.() -> Expression): Expression = + FunctionalExpressionField(this).run(block) + +/** + * Creates a functional expression with this [ExtendedField]. + */ +fun ExtendedField.fieldExpression( + block: FunctionalExpressionExtendedField>.() -> Expression +): Expression = FunctionalExpressionExtendedField(this).run(block) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt new file mode 100644 index 000000000..380822f78 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt @@ -0,0 +1,49 @@ +package scientifik.kmath.expressions + +import scientifik.kmath.operations.Algebra + +/** + * An elementary function that could be invoked on a map of arguments + */ +interface Expression { + /** + * Calls this expression from arguments. + * + * @param arguments the map of arguments. + * @return the value. + */ + operator fun invoke(arguments: Map): T + + companion object +} + +/** + * Create simple lazily evaluated expression inside given algebra + */ +fun Algebra.expression(block: Algebra.(arguments: Map) -> T): Expression = + object : Expression { + override fun invoke(arguments: Map): T = block(arguments) + } + +/** + * Calls this expression from arguments. + * + * @param pairs the pair of arguments' names to values. + * @return the value. + */ +operator fun Expression.invoke(vararg pairs: Pair): T = invoke(mapOf(*pairs)) + +/** + * A context for expression construction + */ +interface ExpressionAlgebra : Algebra { + /** + * Introduce a variable into expression context + */ + fun variable(name: String, default: T? = null): E + + /** + * A constant expression which does not depend on arguments + */ + fun const(value: T): E +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/FunctionalExpressionAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/FunctionalExpressionAlgebra.kt new file mode 100644 index 000000000..9fe8aaf93 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/FunctionalExpressionAlgebra.kt @@ -0,0 +1,169 @@ +package scientifik.kmath.expressions + +import scientifik.kmath.operations.* + +internal class FunctionalUnaryOperation(val context: Algebra, val name: String, private val expr: Expression) : + Expression { + override fun invoke(arguments: Map): T = context.unaryOperation(name, expr.invoke(arguments)) +} + +internal class FunctionalBinaryOperation( + val context: Algebra, + val name: String, + val first: Expression, + val second: Expression +) : Expression { + override fun invoke(arguments: Map): T = + context.binaryOperation(name, first.invoke(arguments), second.invoke(arguments)) +} + +internal class FunctionalVariableExpression(val name: String, val default: T? = null) : Expression { + override fun invoke(arguments: Map): T = + arguments[name] ?: default ?: error("Parameter not found: $name") +} + +internal class FunctionalConstantExpression(val value: T) : Expression { + override fun invoke(arguments: Map): T = value +} + +internal class FunctionalConstProductExpression( + val context: Space, + private val expr: Expression, + val const: Number +) : Expression { + override fun invoke(arguments: Map): T = context.multiply(expr.invoke(arguments), const) +} + +/** + * A context class for [Expression] construction. + * + * @param algebra The algebra to provide for Expressions built. + */ +abstract class FunctionalExpressionAlgebra>(val algebra: A) : ExpressionAlgebra> { + /** + * Builds an Expression of constant expression which does not depend on arguments. + */ + override fun const(value: T): Expression = FunctionalConstantExpression(value) + + /** + * Builds an Expression to access a variable. + */ + override fun variable(name: String, default: T?): Expression = FunctionalVariableExpression(name, default) + + /** + * Builds an Expression of dynamic call of binary operation [operation] on [left] and [right]. + */ + override fun binaryOperation(operation: String, left: Expression, right: Expression): Expression = + FunctionalBinaryOperation(algebra, operation, left, right) + + /** + * Builds an Expression of dynamic call of unary operation with name [operation] on [arg]. + */ + override fun unaryOperation(operation: String, arg: Expression): Expression = + FunctionalUnaryOperation(algebra, operation, arg) +} + +/** + * A context class for [Expression] construction for [Space] algebras. + */ +open class FunctionalExpressionSpace>(algebra: A) : + FunctionalExpressionAlgebra(algebra), Space> { + override val zero: Expression get() = const(algebra.zero) + + /** + * Builds an Expression of addition of two another expressions. + */ + override fun add(a: Expression, b: Expression): Expression = + binaryOperation(SpaceOperations.PLUS_OPERATION, a, b) + + /** + * Builds an Expression of multiplication of expression by number. + */ + override fun multiply(a: Expression, k: Number): Expression = + FunctionalConstProductExpression(algebra, a, k) + + operator fun Expression.plus(arg: T): Expression = this + const(arg) + operator fun Expression.minus(arg: T): Expression = this - const(arg) + operator fun T.plus(arg: Expression): Expression = arg + this + operator fun T.minus(arg: Expression): Expression = arg - this + + override fun unaryOperation(operation: String, arg: Expression): Expression = + super.unaryOperation(operation, arg) + + override fun binaryOperation(operation: String, left: Expression, right: Expression): Expression = + super.binaryOperation(operation, left, right) +} + +open class FunctionalExpressionRing(algebra: A) : FunctionalExpressionSpace(algebra), + Ring> where A : Ring, A : NumericAlgebra { + override val one: Expression + get() = const(algebra.one) + + /** + * Builds an Expression of multiplication of two expressions. + */ + override fun multiply(a: Expression, b: Expression): Expression = + binaryOperation(RingOperations.TIMES_OPERATION, a, b) + + operator fun Expression.times(arg: T): Expression = this * const(arg) + operator fun T.times(arg: Expression): Expression = arg * this + + override fun unaryOperation(operation: String, arg: Expression): Expression = + super.unaryOperation(operation, arg) + + override fun binaryOperation(operation: String, left: Expression, right: Expression): Expression = + super.binaryOperation(operation, left, right) +} + +open class FunctionalExpressionField(algebra: A) : + FunctionalExpressionRing(algebra), + Field> where A : Field, A : NumericAlgebra { + /** + * Builds an Expression of division an expression by another one. + */ + override fun divide(a: Expression, b: Expression): Expression = + binaryOperation(FieldOperations.DIV_OPERATION, a, b) + + operator fun Expression.div(arg: T): Expression = this / const(arg) + operator fun T.div(arg: Expression): Expression = arg / this + + override fun unaryOperation(operation: String, arg: Expression): Expression = + super.unaryOperation(operation, arg) + + override fun binaryOperation(operation: String, left: Expression, right: Expression): Expression = + super.binaryOperation(operation, left, right) +} + +open class FunctionalExpressionExtendedField(algebra: A) : + FunctionalExpressionField(algebra), + ExtendedField> where A : ExtendedField, A : NumericAlgebra { + override fun sin(arg: Expression): Expression = unaryOperation(TrigonometricOperations.SIN_OPERATION, arg) + override fun cos(arg: Expression): Expression = unaryOperation(TrigonometricOperations.COS_OPERATION, arg) + override fun asin(arg: Expression): Expression = unaryOperation(TrigonometricOperations.ASIN_OPERATION, arg) + override fun acos(arg: Expression): Expression = unaryOperation(TrigonometricOperations.ACOS_OPERATION, arg) + override fun atan(arg: Expression): Expression = unaryOperation(TrigonometricOperations.ATAN_OPERATION, arg) + + override fun power(arg: Expression, pow: Number): Expression = + binaryOperation(PowerOperations.POW_OPERATION, arg, number(pow)) + + override fun exp(arg: Expression): Expression = unaryOperation(ExponentialOperations.EXP_OPERATION, arg) + override fun ln(arg: Expression): Expression = unaryOperation(ExponentialOperations.LN_OPERATION, arg) + + override fun unaryOperation(operation: String, arg: Expression): Expression = + super.unaryOperation(operation, arg) + + override fun binaryOperation(operation: String, left: Expression, right: Expression): Expression = + super.binaryOperation(operation, left, right) +} + +inline fun > A.expressionInSpace(block: FunctionalExpressionSpace.() -> Expression): Expression = + FunctionalExpressionSpace(this).block() + +inline fun > A.expressionInRing(block: FunctionalExpressionRing.() -> Expression): Expression = + FunctionalExpressionRing(this).block() + +inline fun > A.expressionInField(block: FunctionalExpressionField.() -> Expression): Expression = + FunctionalExpressionField(this).block() + +inline fun > A.expressionInExtendedField(block: FunctionalExpressionExtendedField.() -> Expression): Expression = + FunctionalExpressionExtendedField(this).block() diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt new file mode 100644 index 000000000..2e1f32501 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt @@ -0,0 +1,122 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.* + +/** + * Basic implementation of Matrix space based on [NDStructure] + */ +class BufferMatrixContext>( + override val elementContext: R, + private val bufferFactory: BufferFactory +) : GenericMatrixContext { + + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): BufferMatrix { + val buffer = bufferFactory(rows * columns) { offset -> initializer(offset / columns, offset % columns) } + return BufferMatrix(rows, columns, buffer) + } + + override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) + + companion object +} + +@Suppress("OVERRIDE_BY_INLINE") +object RealMatrixContext : GenericMatrixContext { + + override val elementContext: RealField get() = RealField + + override inline fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): Matrix { + val buffer = RealBuffer(rows * columns) { offset -> initializer(offset / columns, offset % columns) } + return BufferMatrix(rows, columns, buffer) + } + + override inline fun point(size: Int, initializer: (Int) -> Double): Point = RealBuffer(size, initializer) +} + +class BufferMatrix( + override val rowNum: Int, + override val colNum: Int, + val buffer: Buffer, + override val features: Set = emptySet() +) : FeaturedMatrix { + + init { + if (buffer.size != rowNum * colNum) { + error("Dimension mismatch for matrix structure") + } + } + + override val shape: IntArray get() = intArrayOf(rowNum, colNum) + + override fun suggestFeature(vararg features: MatrixFeature): BufferMatrix = + BufferMatrix(rowNum, colNum, buffer, this.features + features) + + override fun get(index: IntArray): T = get(index[0], index[1]) + + override fun get(i: Int, j: Int): T = buffer[i * colNum + j] + + override fun elements(): Sequence> = sequence { + for (i in 0 until rowNum) { + for (j in 0 until colNum) { + yield(intArrayOf(i, j) to get(i, j)) + } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return when (other) { + is NDStructure<*> -> return NDStructure.equals(this, other) + else -> false + } + } + + override fun hashCode(): Int { + var result = buffer.hashCode() + result = 31 * result + features.hashCode() + return result + } + + override fun toString(): String { + return if (rowNum <= 5 && colNum <= 5) { + "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)\n" + + rows.asSequence().joinToString(prefix = "(", postfix = ")", separator = "\n ") { buffer -> + buffer.asSequence().joinToString(separator = "\t") { it.toString() } + } + } else { + "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)" + } + } +} + +/** + * Optimized dot product for real matrices + */ +infix fun BufferMatrix.dot(other: BufferMatrix): BufferMatrix { + if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})") + + val array = DoubleArray(this.rowNum * other.colNum) + + //convert to array to insure there is not memory indirection + fun Buffer.unsafeArray(): DoubleArray = if (this is RealBuffer) { + array + } else { + DoubleArray(size) { get(it) } + } + + val a = this.buffer.unsafeArray() + val b = other.buffer.unsafeArray() + + for (i in (0 until rowNum)) { + for (j in (0 until other.colNum)) { + for (k in (0 until colNum)) { + array[i * other.colNum + j] += a[i * colNum + k] * b[k * other.colNum + j] + } + } + } + + val buffer = RealBuffer(array) + return BufferMatrix(rowNum, other.colNum, buffer) +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt new file mode 100644 index 000000000..dd17c4fe7 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt @@ -0,0 +1,84 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.Structure2D +import scientifik.kmath.structures.asBuffer +import kotlin.math.sqrt + +/** + * A 2d structure plus optional matrix-specific features + */ +interface FeaturedMatrix : Matrix { + + override val shape: IntArray get() = intArrayOf(rowNum, colNum) + + val features: Set + + /** + * Suggest new feature for this matrix. The result is the new matrix that may or may not reuse existing data structure. + * + * The implementation does not guarantee to check that matrix actually have the feature, so one should be careful to + * add only those features that are valid. + */ + fun suggestFeature(vararg features: MatrixFeature): FeaturedMatrix + + companion object +} + +fun Structure2D.Companion.real(rows: Int, columns: Int, initializer: (Int, Int) -> Double): Matrix = + MatrixContext.real.produce(rows, columns, initializer) + +/** + * Build a square matrix from given elements. + */ +fun Structure2D.Companion.square(vararg elements: T): FeaturedMatrix { + val size: Int = sqrt(elements.size.toDouble()).toInt() + if (size * size != elements.size) error("The number of elements ${elements.size} is not a full square") + val buffer = elements.asBuffer() + return BufferMatrix(size, size, buffer) +} + +val Matrix<*>.features: Set get() = (this as? FeaturedMatrix)?.features ?: emptySet() + +/** + * Check if matrix has the given feature class + */ +inline fun Matrix<*>.hasFeature(): Boolean = + features.find { it is T } != null + +/** + * Get the first feature matching given class. Does not guarantee that matrix has only one feature matching the criteria + */ +inline fun Matrix<*>.getFeature(): T? = + features.filterIsInstance().firstOrNull() + +/** + * Diagonal matrix of ones. The matrix is virtual no actual matrix is created + */ +fun > GenericMatrixContext.one(rows: Int, columns: Int): FeaturedMatrix = + VirtualMatrix(rows, columns, DiagonalFeature) { i, j -> + if (i == j) elementContext.one else elementContext.zero + } + + +/** + * A virtual matrix of zeroes + */ +fun > GenericMatrixContext.zero(rows: Int, columns: Int): FeaturedMatrix = + VirtualMatrix(rows, columns) { _, _ -> elementContext.zero } + +class TransposedFeature(val original: Matrix) : MatrixFeature + +/** + * Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A` + */ +fun Matrix.transpose(): Matrix { + return this.getFeature>()?.original ?: VirtualMatrix( + this.colNum, + this.rowNum, + setOf(TransposedFeature(this)) + ) { i, j -> get(j, i) } +} + +infix fun Matrix.dot(other: Matrix): Matrix = with(MatrixContext.real) { dot(other) } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt similarity index 51% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LUPDecomposition.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index 099fa1909..d04a99fbb 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -1,19 +1,25 @@ -package kscience.kmath.linear +package scientifik.kmath.linear -import kscience.kmath.operations.* -import kscience.kmath.structures.* +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.BufferAccessor2D +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.Structure2D +import kotlin.reflect.KClass /** * Common implementation of [LUPDecompositionFeature] */ -public class LUPDecomposition( - public val context: MatrixContext>, - public val elementContext: Field, - public val lu: Structure2D, - public val pivot: IntArray, - private val even: Boolean, +class LUPDecomposition( + val context: GenericMatrixContext>, + val lu: Structure2D, + val pivot: IntArray, + private val even: Boolean ) : LUPDecompositionFeature, DeterminantFeature { + val elementContext: Field get() = context.elementContext + /** * Returns the matrix L of the decomposition. * @@ -37,6 +43,7 @@ public class LUPDecomposition( if (j >= i) lu[i, j] else elementContext.zero } + /** * Returns the P rows permutation matrix. * @@ -47,86 +54,100 @@ public class LUPDecomposition( if (j == pivot[i]) elementContext.one else elementContext.zero } + /** * Return the determinant of the matrix * @return determinant of the matrix */ override val determinant: T by lazy { - elementContext { (0 until lu.shape[0]).fold(if (even) one else -one) { value, i -> value * lu[i, i] } } + with(elementContext) { + (0 until lu.shape[0]).fold(if (even) one else -one) { value, i -> value * lu[i, i] } + } } } -@PublishedApi -internal fun , F : Field> GenericMatrixContext.abs(value: T): T = - if (value > elementContext.zero) value else elementContext { -value } +fun , F : Field> GenericMatrixContext.abs(value: T): T = + if (value > elementContext.zero) value else with(elementContext) { -value } + /** - * Create a lup decomposition of generic matrix. + * Create a lup decomposition of generic matrix */ -public fun > MatrixContext>.lup( - factory: MutableBufferFactory, - elementContext: Field, +fun , F : Field> GenericMatrixContext.lup( + type: KClass, matrix: Matrix, - checkSingular: (T) -> Boolean, + checkSingular: (T) -> Boolean ): LUPDecomposition { - require(matrix.rowNum == matrix.colNum) { "LU decomposition supports only square matrices" } + if (matrix.rowNum != matrix.colNum) { + error("LU decomposition supports only square matrices") + } + val m = matrix.colNum val pivot = IntArray(matrix.rowNum) //TODO just waits for KEEP-176 - BufferAccessor2D(matrix.rowNum, matrix.colNum, factory).run { - elementContext { + BufferAccessor2D(type, matrix.rowNum, matrix.colNum).run { + elementContext.run { + val lu = create(matrix) // Initialize permutation array and parity - for (row in 0 until m) pivot[row] = row + for (row in 0 until m) { + pivot[row] = row + } var even = true // Initialize permutation array and parity - for (row in 0 until m) pivot[row] = row + for (row in 0 until m) { + pivot[row] = row + } // Loop over columns for (col in 0 until m) { + // upper for (row in 0 until col) { val luRow = lu.row(row) var sum = luRow[col] - for (i in 0 until row) sum -= luRow[i] * lu[i, col] + for (i in 0 until row) { + sum -= luRow[i] * lu[i, col] + } luRow[col] = sum } // lower var max = col // permutation row var largest = -one - for (row in col until m) { val luRow = lu.row(row) var sum = luRow[col] - for (i in 0 until col) sum -= luRow[i] * lu[i, col] + for (i in 0 until col) { + sum -= luRow[i] * lu[i, col] + } luRow[col] = sum // maintain best permutation choice - if (abs(sum) > largest) { - largest = abs(sum) + if (this@lup.abs(sum) > largest) { + largest = this@lup.abs(sum) max = row } } // Singularity check - check(!checkSingular(abs(lu[max, col]))) { "The matrix is singular" } + if (checkSingular(this@lup.abs(lu[max, col]))) { + error("The matrix is singular") + } // Pivot if necessary if (max != col) { val luMax = lu.row(max) val luCol = lu.row(col) - for (i in 0 until m) { val tmp = luMax[i] luMax[i] = luCol[i] luCol[i] = tmp } - val temp = pivot[max] pivot[max] = pivot[col] pivot[col] = temp @@ -135,40 +156,47 @@ public fun > MatrixContext>.lup( // Divide the lower elements by the "winning" diagonal elt. val luDiag = lu[col, col] - for (row in col + 1 until m) lu[row, col] /= luDiag + for (row in col + 1 until m) { + lu[row, col] /= luDiag + } } - return LUPDecomposition(this@lup, elementContext, lu.collect(), pivot, even) + return LUPDecomposition(this@lup, lu.collect(), pivot, even) } } } -public inline fun , F : Field> GenericMatrixContext>.lup( +inline fun , F : Field> GenericMatrixContext.lup( matrix: Matrix, - noinline checkSingular: (T) -> Boolean, -): LUPDecomposition = lup(MutableBuffer.Companion::auto, elementContext, matrix, checkSingular) + noinline checkSingular: (T) -> Boolean +): LUPDecomposition = lup(T::class, matrix, checkSingular) -public fun MatrixContext>.lup(matrix: Matrix): LUPDecomposition = - lup(Buffer.Companion::real, RealField, matrix) { it < 1e-11 } +fun GenericMatrixContext.lup(matrix: Matrix): LUPDecomposition = + lup(Double::class, matrix) { it < 1e-11 } -public fun LUPDecomposition.solveWithLUP(factory: MutableBufferFactory, matrix: Matrix): FeaturedMatrix { - require(matrix.rowNum == pivot.size) { "Matrix dimension mismatch. Expected ${pivot.size}, but got ${matrix.colNum}" } +fun LUPDecomposition.solve(type: KClass, matrix: Matrix): Matrix { + + if (matrix.rowNum != pivot.size) { + error("Matrix dimension mismatch. Expected ${pivot.size}, but got ${matrix.colNum}") + } + + BufferAccessor2D(type, matrix.rowNum, matrix.colNum).run { + elementContext.run { - BufferAccessor2D(matrix.rowNum, matrix.colNum, factory).run { - elementContext { // Apply permutations to b val bp = create { _, _ -> zero } for (row in pivot.indices) { val bpRow = bp.row(row) val pRow = pivot[row] - for (col in 0 until matrix.colNum) bpRow[col] = matrix[pRow, col] + for (col in 0 until matrix.colNum) { + bpRow[col] = matrix[pRow, col] + } } // Solve LY = b for (col in pivot.indices) { val bpCol = bp.row(col) - for (i in col + 1 until pivot.size) { val bpI = bp.row(i) val luICol = lu[i, col] @@ -182,48 +210,43 @@ public fun LUPDecomposition.solveWithLUP(factory: MutableBufferFact for (col in pivot.size - 1 downTo 0) { val bpCol = bp.row(col) val luDiag = lu[col, col] - for (j in 0 until matrix.colNum) bpCol[j] /= luDiag - + for (j in 0 until matrix.colNum) { + bpCol[j] /= luDiag + } for (i in 0 until col) { val bpI = bp.row(i) val luICol = lu[i, col] - for (j in 0 until matrix.colNum) bpI[j] -= bpCol[j] * luICol + for (j in 0 until matrix.colNum) { + bpI[j] -= bpCol[j] * luICol + } } } - return context.produce(pivot.size, matrix.colNum) { i, j -> bp[i, j] } } } } -public inline fun LUPDecomposition.solveWithLUP(matrix: Matrix): Matrix = - solveWithLUP(MutableBuffer.Companion::auto, matrix) +inline fun LUPDecomposition.solve(matrix: Matrix): Matrix = solve(T::class, matrix) /** - * Solve a linear equation **a*x = b** using LUP decomposition + * Solve a linear equation **a*x = b** */ -public inline fun , F : Field> GenericMatrixContext>.solveWithLUP( +inline fun , F : Field> GenericMatrixContext.solve( a: Matrix, b: Matrix, - noinline bufferFactory: MutableBufferFactory = MutableBuffer.Companion::auto, - noinline checkSingular: (T) -> Boolean, -): FeaturedMatrix { + noinline checkSingular: (T) -> Boolean +): Matrix { // Use existing decomposition if it is provided by matrix - val decomposition = a.getFeature() ?: lup(bufferFactory, elementContext, a, checkSingular) - return decomposition.solveWithLUP(bufferFactory, b) + val decomposition = a.getFeature() ?: lup(T::class, a, checkSingular) + return decomposition.solve(T::class, b) } -public fun RealMatrixContext.solveWithLUP(a: Matrix, b: Matrix): FeaturedMatrix = - solveWithLUP(a, b) { it < 1e-11 } +fun RealMatrixContext.solve(a: Matrix, b: Matrix): Matrix = solve(a, b) { it < 1e-11 } -public inline fun , F : Field> GenericMatrixContext>.inverseWithLUP( +inline fun , F : Field> GenericMatrixContext.inverse( matrix: Matrix, - noinline bufferFactory: MutableBufferFactory = MutableBuffer.Companion::auto, - noinline checkSingular: (T) -> Boolean, -): FeaturedMatrix = solveWithLUP(matrix, one(matrix.rowNum, matrix.colNum), bufferFactory, checkSingular) + noinline checkSingular: (T) -> Boolean +): Matrix = solve(matrix, one(matrix.rowNum, matrix.colNum), checkSingular) -/** - * Inverses a square matrix using LUP decomposition. Non square matrix will throw a error. - */ -public fun RealMatrixContext.inverseWithLUP(matrix: Matrix): FeaturedMatrix = - solveWithLUP(matrix, one(matrix.rowNum, matrix.colNum), Buffer.Companion::real) { it < 1e-11 } +fun RealMatrixContext.inverse(matrix: Matrix): Matrix = + solve(matrix, one(matrix.rowNum, matrix.colNum)) { it < 1e-11 } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgebra.kt new file mode 100644 index 000000000..fb49d18ed --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgebra.kt @@ -0,0 +1,28 @@ +package scientifik.kmath.linear + +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.VirtualBuffer + +typealias Point = Buffer + +/** + * A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors + */ +interface LinearSolver { + fun solve(a: Matrix, b: Matrix): Matrix + fun solve(a: Matrix, b: Point): Point = solve(a, b.asMatrix()).asPoint() + fun inverse(a: Matrix): Matrix +} + +/** + * Convert matrix to vector if it is possible + */ +fun Matrix.asPoint(): Point = + if (this.colNum == 1) { + VirtualBuffer(rowNum) { get(it, 0) } + } else { + error("Can't convert matrix with more than one column to vector") + } + +fun Point.asMatrix(): VirtualMatrix = VirtualMatrix(size, 1) { i, _ -> get(i) } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt new file mode 100644 index 000000000..516f65bb8 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt @@ -0,0 +1,46 @@ +package scientifik.kmath.linear + +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory +import scientifik.kmath.structures.Structure2D +import scientifik.kmath.structures.asBuffer + +class MatrixBuilder(val rows: Int, val columns: Int) { + operator fun invoke(vararg elements: T): FeaturedMatrix { + if (rows * columns != elements.size) error("The number of elements ${elements.size} is not equal $rows * $columns") + val buffer = elements.asBuffer() + return BufferMatrix(rows, columns, buffer) + } + + //TODO add specific matrix builder functions like diagonal, etc +} + +fun Structure2D.Companion.build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns) + +fun Structure2D.Companion.row(vararg values: T): FeaturedMatrix { + val buffer = values.asBuffer() + return BufferMatrix(1, values.size, buffer) +} + +inline fun Structure2D.Companion.row( + size: Int, + factory: BufferFactory = Buffer.Companion::auto, + noinline builder: (Int) -> T +): FeaturedMatrix { + val buffer = factory(size, builder) + return BufferMatrix(1, size, buffer) +} + +fun Structure2D.Companion.column(vararg values: T): FeaturedMatrix { + val buffer = values.asBuffer() + return BufferMatrix(values.size, 1, buffer) +} + +inline fun Structure2D.Companion.column( + size: Int, + factory: BufferFactory = Buffer.Companion::auto, + noinline builder: (Int) -> T +): FeaturedMatrix { + val buffer = factory(size, builder) + return BufferMatrix(size, 1, buffer) +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt new file mode 100644 index 000000000..5dc86a7dd --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt @@ -0,0 +1,105 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.SpaceOperations +import scientifik.kmath.operations.sum +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.asSequence + +/** + * Basic operations on matrices. Operates on [Matrix] + */ +interface MatrixContext : SpaceOperations> { + /** + * Produce a matrix with this context and given dimensions + */ + fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix + + infix fun Matrix.dot(other: Matrix): Matrix + + infix fun Matrix.dot(vector: Point): Point + + operator fun Matrix.times(value: T): Matrix + + operator fun T.times(m: Matrix): Matrix = m * this + + companion object { + /** + * Non-boxing double matrix + */ + val real: RealMatrixContext = RealMatrixContext + + /** + * A structured matrix with custom buffer + */ + fun > buffered( + ring: R, + bufferFactory: BufferFactory = Buffer.Companion::boxing + ): GenericMatrixContext = + BufferMatrixContext(ring, bufferFactory) + + /** + * Automatic buffered matrix, unboxed if it is possible + */ + inline fun > auto(ring: R): GenericMatrixContext = + buffered(ring, Buffer.Companion::auto) + } +} + +interface GenericMatrixContext> : MatrixContext { + /** + * The ring context for matrix elements + */ + val elementContext: R + + /** + * Produce a point compatible with matrix space + */ + fun point(size: Int, initializer: (Int) -> T): Point + + override infix fun Matrix.dot(other: Matrix): Matrix { + //TODO add typed error + if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})") + return produce(rowNum, other.colNum) { i, j -> + val row = rows[i] + val column = other.columns[j] + with(elementContext) { + sum(row.asSequence().zip(column.asSequence(), ::multiply)) + } + } + } + + override infix fun Matrix.dot(vector: Point): Point { + //TODO add typed error + if (this.colNum != vector.size) error("Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})") + return point(rowNum) { i -> + val row = rows[i] + with(elementContext) { + sum(row.asSequence().zip(vector.asSequence(), ::multiply)) + } + } + } + + override operator fun Matrix.unaryMinus(): Matrix = + produce(rowNum, colNum) { i, j -> elementContext.run { -get(i, j) } } + + override fun add(a: Matrix, b: Matrix): Matrix { + if (a.rowNum != b.rowNum || a.colNum != b.colNum) error("Matrix operation dimension mismatch. [${a.rowNum},${a.colNum}] + [${b.rowNum},${b.colNum}]") + return produce(a.rowNum, a.colNum) { i, j -> elementContext.run { a[i, j] + b[i, j] } } + } + + override operator fun Matrix.minus(b: Matrix): Matrix { + if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] - [${b.rowNum},${b.colNum}]") + return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } } + } + + override fun multiply(a: Matrix, k: Number): Matrix = + produce(a.rowNum, a.colNum) { i, j -> elementContext.run { a[i, j] * k } } + + operator fun Number.times(matrix: FeaturedMatrix): Matrix = matrix * this + + override fun Matrix.times(value: T): Matrix = + produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } } +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt new file mode 100644 index 000000000..87cfe21b0 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt @@ -0,0 +1,62 @@ +package scientifik.kmath.linear + +/** + * A marker interface representing some matrix feature like diagonal, sparse, zero, etc. Features used to optimize matrix + * operations performance in some cases. + */ +interface MatrixFeature + +/** + * The matrix with this feature is considered to have only diagonal non-null elements + */ +object DiagonalFeature : MatrixFeature + +/** + * Matrix with this feature has all zero elements + */ +object ZeroFeature : MatrixFeature + +/** + * Matrix with this feature have unit elements on diagonal and zero elements in all other places + */ +object UnitFeature : MatrixFeature + +/** + * Inverted matrix feature + */ +interface InverseMatrixFeature : MatrixFeature { + val inverse: FeaturedMatrix +} + +/** + * A determinant container + */ +interface DeterminantFeature : MatrixFeature { + val determinant: T +} + +@Suppress("FunctionName") +fun DeterminantFeature(determinant: T): DeterminantFeature = object : DeterminantFeature { + override val determinant: T = determinant +} + +/** + * Lower triangular matrix + */ +object LFeature : MatrixFeature + +/** + * Upper triangular feature + */ +object UFeature : MatrixFeature + +/** + * TODO add documentation + */ +interface LUPDecompositionFeature : MatrixFeature { + val l: FeaturedMatrix + val u: FeaturedMatrix + val p: FeaturedMatrix +} + +//TODO add sparse matrix feature diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VectorSpace.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VectorSpace.kt new file mode 100644 index 000000000..691b464fc --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VectorSpace.kt @@ -0,0 +1,75 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Space +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory + +/** + * A linear space for vectors. + * Could be used on any point-like structure + */ +interface VectorSpace> : Space> { + + val size: Int + + val space: S + + fun produce(initializer: (Int) -> T): Point + + /** + * Produce a space-element of this vector space for expressions + */ + //fun produceElement(initializer: (Int) -> T): Vector + + override val zero: Point get() = produce { space.zero } + + override fun add(a: Point, b: Point): Point = produce { with(space) { a[it] + b[it] } } + + override fun multiply(a: Point, k: Number): Point = produce { with(space) { a[it] * k } } + + //TODO add basis + + companion object { + + private val realSpaceCache = HashMap>() + + /** + * Non-boxing double vector space + */ + fun real(size: Int): BufferVectorSpace { + return realSpaceCache.getOrPut(size) { + BufferVectorSpace( + size, + RealField, + Buffer.Companion::auto + ) + } + } + + /** + * A structured vector space with custom buffer + */ + fun > buffered( + size: Int, + space: S, + bufferFactory: BufferFactory = Buffer.Companion::boxing + ): BufferVectorSpace = BufferVectorSpace(size, space, bufferFactory) + + /** + * Automatic buffered vector, unboxed if it is possible + */ + inline fun > auto(size: Int, space: S): VectorSpace = + buffered(size, space, Buffer.Companion::auto) + } +} + + +class BufferVectorSpace>( + override val size: Int, + override val space: S, + val bufferFactory: BufferFactory +) : VectorSpace { + override fun produce(initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) + //override fun produceElement(initializer: (Int) -> T): Vector = BufferVector(this, produce(initializer)) +} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt similarity index 67% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VirtualMatrix.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt index e0a1d0026..207151d57 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VirtualMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt @@ -1,19 +1,15 @@ -package kscience.kmath.linear +package scientifik.kmath.linear -import kscience.kmath.structures.Matrix +import scientifik.kmath.structures.Matrix -public class VirtualMatrix( +class VirtualMatrix( override val rowNum: Int, override val colNum: Int, override val features: Set = emptySet(), - public val generator: (i: Int, j: Int) -> T + val generator: (i: Int, j: Int) -> T ) : FeaturedMatrix { - public constructor( - rowNum: Int, - colNum: Int, - vararg features: MatrixFeature, - generator: (i: Int, j: Int) -> T - ) : this( + + constructor(rowNum: Int, colNum: Int, vararg features: MatrixFeature, generator: (i: Int, j: Int) -> T) : this( rowNum, colNum, setOf(*features), @@ -22,7 +18,7 @@ public class VirtualMatrix( override val shape: IntArray get() = intArrayOf(rowNum, colNum) - override operator fun get(i: Int, j: Int): T = generator(i, j) + override fun get(i: Int, j: Int): T = generator(i, j) override fun suggestFeature(vararg features: MatrixFeature): VirtualMatrix = VirtualMatrix(rowNum, colNum, this.features + features, generator) @@ -46,15 +42,18 @@ public class VirtualMatrix( } - public companion object { + companion object { /** * Wrap a matrix adding additional features to it */ - public fun wrap(matrix: Matrix, vararg features: MatrixFeature): FeaturedMatrix { - return if (matrix is VirtualMatrix) + fun wrap(matrix: Matrix, vararg features: MatrixFeature): FeaturedMatrix { + return if (matrix is VirtualMatrix) { VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features, matrix.generator) - else - VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features) { i, j -> matrix[i, j] } + } else { + VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features) { i, j -> + matrix[i, j] + } + } } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/AutoDiff.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/AutoDiff.kt new file mode 100644 index 000000000..db8863ae8 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/AutoDiff.kt @@ -0,0 +1,239 @@ +package scientifik.kmath.misc + +import scientifik.kmath.linear.Point +import scientifik.kmath.operations.ExtendedField +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.sum +import scientifik.kmath.structures.asBuffer + +/* + * Implementation of backward-mode automatic differentiation. + * Initial gist by Roman Elizarov: https://gist.github.com/elizarov/1ad3a8583e88cb6ea7a0ad09bb591d3d + */ + +/** + * Differentiable variable with value and derivative of differentiation ([deriv]) result + * with respect to this variable. + */ +open class Variable(val value: T) + +class DerivationResult( + value: T, + val deriv: Map, T>, + val context: Field +) : Variable(value) { + fun deriv(variable: Variable): T = deriv[variable] ?: context.zero + + /** + * compute divergence + */ + fun div(): T = context.run { sum(deriv.values) } + + /** + * Compute a gradient for variables in given order + */ + fun grad(vararg variables: Variable): Point = if (variables.isEmpty()) { + error("Variable order is not provided for gradient construction") + } else { + variables.map(::deriv).asBuffer() + } +} + +/** + * Runs differentiation and establishes [AutoDiffField] context inside the block of code. + * + * The partial derivatives are placed in argument `d` variable + * + * Example: + * ``` + * val x = Variable(2) // define variable(s) and their values + * val y = deriv { sqr(x) + 5 * x + 3 } // write formulate in deriv context + * assertEquals(17.0, y.x) // the value of result (y) + * assertEquals(9.0, x.d) // dy/dx + * ``` + */ +fun > F.deriv(body: AutoDiffField.() -> Variable): DerivationResult = + AutoDiffContext(this).run { + val result = body() + result.d = context.one// computing derivative w.r.t result + runBackwardPass() + DerivationResult(result.value, derivatives, this@deriv) + } + + +abstract class AutoDiffField> : Field> { + + abstract val context: F + + /** + * Performs update of derivative after the rest of the formula in the back-pass. + * + * For example, implementation of `sin` function is: + * + * ``` + * fun AD.sin(x: Variable): Variable = derive(Variable(sin(x.x)) { z -> // call derive with function result + * x.d += z.d * cos(x.x) // update derivative using chain rule and derivative of the function + * } + * ``` + */ + abstract fun derive(value: R, block: F.(R) -> Unit): R + + /** + * A variable accessing inner state of derivatives. + * Use this function in inner builders to avoid creating additional derivative bindings + */ + abstract var Variable.d: T + + abstract fun variable(value: T): Variable + + inline fun variable(block: F.() -> T): Variable = variable(context.block()) + + // Overloads for Double constants + + override operator fun Number.plus(b: Variable): Variable = + derive(variable { this@plus.toDouble() * one + b.value }) { z -> + b.d += z.d + } + + override operator fun Variable.plus(b: Number): Variable = b.plus(this) + + override operator fun Number.minus(b: Variable): Variable = + derive(variable { this@minus.toDouble() * one - b.value }) { z -> + b.d -= z.d + } + + override operator fun Variable.minus(b: Number): Variable = + derive(variable { this@minus.value - one * b.toDouble() }) { z -> + this@minus.d += z.d + } +} + +/** + * Automatic Differentiation context class. + */ +private class AutoDiffContext>(override val context: F) : AutoDiffField() { + + // this stack contains pairs of blocks and values to apply them to + private var stack = arrayOfNulls(8) + private var sp = 0 + + internal val derivatives = HashMap, T>() + + + /** + * A variable coupled with its derivative. For internal use only + */ + private class VariableWithDeriv(x: T, var d: T) : Variable(x) + + + override fun variable(value: T): Variable = + VariableWithDeriv(value, context.zero) + + override var Variable.d: T + get() = (this as? VariableWithDeriv)?.d ?: derivatives[this] ?: context.zero + set(value) { + if (this is VariableWithDeriv) { + d = value + } else { + derivatives[this] = value + } + } + + @Suppress("UNCHECKED_CAST") + override fun derive(value: R, block: F.(R) -> Unit): R { + // save block to stack for backward pass + if (sp >= stack.size) stack = stack.copyOf(stack.size * 2) + stack[sp++] = block + stack[sp++] = value + return value + } + + @Suppress("UNCHECKED_CAST") + fun runBackwardPass() { + while (sp > 0) { + val value = stack[--sp] + val block = stack[--sp] as F.(Any?) -> Unit + context.block(value) + } + } + + // Basic math (+, -, *, /) + + + override fun add(a: Variable, b: Variable): Variable = + derive(variable { a.value + b.value }) { z -> + a.d += z.d + b.d += z.d + } + + override fun multiply(a: Variable, b: Variable): Variable = + derive(variable { a.value * b.value }) { z -> + a.d += z.d * b.value + b.d += z.d * a.value + } + + override fun divide(a: Variable, b: Variable): Variable = + derive(variable { a.value / b.value }) { z -> + a.d += z.d / b.value + b.d -= z.d * a.value / (b.value * b.value) + } + + override fun multiply(a: Variable, k: Number): Variable = + derive(variable { k.toDouble() * a.value }) { z -> + a.d += z.d * k.toDouble() + } + + override val zero: Variable get() = Variable(context.zero) + override val one: Variable get() = Variable(context.one) +} + +// Extensions for differentiation of various basic mathematical functions + +// x ^ 2 +fun > AutoDiffField.sqr(x: Variable): Variable = + derive(variable { x.value * x.value }) { z -> + x.d += z.d * 2 * x.value + } + +// x ^ 1/2 +fun > AutoDiffField.sqrt(x: Variable): Variable = + derive(variable { sqrt(x.value) }) { z -> + x.d += z.d * 0.5 / z.value + } + +// x ^ y (const) +fun > AutoDiffField.pow(x: Variable, y: Double): Variable = + derive(variable { power(x.value, y) }) { z -> + x.d += z.d * y * power(x.value, y - 1) + } + +fun > AutoDiffField.pow(x: Variable, y: Int): Variable = pow(x, y.toDouble()) + +// exp(x) +fun > AutoDiffField.exp(x: Variable): Variable = + derive(variable { exp(x.value) }) { z -> + x.d += z.d * z.value + } + +// ln(x) +fun > AutoDiffField.ln(x: Variable): Variable = derive( + variable { ln(x.value) } +) { z -> + x.d += z.d / x.value +} + +// x ^ y (any) +fun > AutoDiffField.pow(x: Variable, y: Variable): Variable = + exp(y * ln(x)) + +// sin(x) +fun > AutoDiffField.sin(x: Variable): Variable = + derive(variable { sin(x.value) }) { z -> + x.d += z.d * cos(x.value) + } + +// cos(x) +fun > AutoDiffField.cos(x: Variable): Variable = + derive(variable { cos(x.value) }) { z -> + x.d -= z.d * sin(x.value) + } diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/grids.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt similarity index 60% rename from kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/grids.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt index 69a149fb8..d3bf0891f 100644 --- a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/grids.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt @@ -1,6 +1,5 @@ -package kscience.kmath.real +package scientifik.kmath.misc -import kscience.kmath.structures.asBuffer import kotlin.math.abs /** @@ -11,21 +10,17 @@ import kotlin.math.abs * * If step is negative, the same goes from upper boundary downwards */ -public fun ClosedFloatingPointRange.toSequenceWithStep(step: Double): Sequence = when { +fun ClosedFloatingPointRange.toSequenceWithStep(step: Double): Sequence = when { step == 0.0 -> error("Zero step in double progression") - step > 0 -> sequence { var current = start - while (current <= endInclusive) { yield(current) current += step } } - else -> sequence { var current = endInclusive - while (current >= start) { yield(current) current += step @@ -33,13 +28,19 @@ public fun ClosedFloatingPointRange.toSequenceWithStep(step: Double): Se } } -public infix fun ClosedFloatingPointRange.step(step: Double): RealVector = - toSequenceWithStep(step).toList().asBuffer() - /** * Convert double range to sequence with the fixed number of points */ -public fun ClosedFloatingPointRange.toSequenceWithPoints(numPoints: Int): Sequence { +fun ClosedFloatingPointRange.toSequenceWithPoints(numPoints: Int): Sequence { require(numPoints > 1) { "The number of points should be more than 2" } return toSequenceWithStep(abs(endInclusive - start) / (numPoints - 1)) } + +/** + * Convert double range to array of evenly spaced doubles, where the size of array equals [numPoints] + */ +@Deprecated("Replace by 'toSequenceWithPoints'") +fun ClosedFloatingPointRange.toGrid(numPoints: Int): DoubleArray { + if (numPoints < 2) error("Can't create generic grid with less than two points") + return DoubleArray(numPoints) { i -> start + (endInclusive - start) / (numPoints - 1) * i } +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/cumulative.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/cumulative.kt new file mode 100644 index 000000000..a0f4525cc --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/cumulative.kt @@ -0,0 +1,77 @@ +package scientifik.kmath.misc + +import scientifik.kmath.operations.Space +import scientifik.kmath.operations.invoke +import kotlin.jvm.JvmName + +/** + * Generic cumulative operation on iterator. + * + * @param T the type of initial iterable. + * @param R the type of resulting iterable. + * @param initial lazy evaluated. + */ +fun Iterator.cumulative(initial: R, operation: (R, T) -> R): Iterator = object : Iterator { + var state: R = initial + override fun hasNext(): Boolean = this@cumulative.hasNext() + + override fun next(): R { + state = operation(state, this@cumulative.next()) + return state + } +} + +fun Iterable.cumulative(initial: R, operation: (R, T) -> R): Iterable = object : Iterable { + override fun iterator(): Iterator = this@cumulative.iterator().cumulative(initial, operation) +} + +fun Sequence.cumulative(initial: R, operation: (R, T) -> R): Sequence = object : Sequence { + override fun iterator(): Iterator = this@cumulative.iterator().cumulative(initial, operation) +} + +fun List.cumulative(initial: R, operation: (R, T) -> R): List = + this.iterator().cumulative(initial, operation).asSequence().toList() + +//Cumulative sum + +/** + * Cumulative sum with custom space + */ +fun Iterable.cumulativeSum(space: Space): Iterable = space { + cumulative(zero) { element: T, sum: T -> sum + element } +} + +@JvmName("cumulativeSumOfDouble") +fun Iterable.cumulativeSum(): Iterable = this.cumulative(0.0) { element, sum -> sum + element } + +@JvmName("cumulativeSumOfInt") +fun Iterable.cumulativeSum(): Iterable = this.cumulative(0) { element, sum -> sum + element } + +@JvmName("cumulativeSumOfLong") +fun Iterable.cumulativeSum(): Iterable = this.cumulative(0L) { element, sum -> sum + element } + +fun Sequence.cumulativeSum(space: Space): Sequence = with(space) { + cumulative(zero) { element: T, sum: T -> sum + element } +} + +@JvmName("cumulativeSumOfDouble") +fun Sequence.cumulativeSum(): Sequence = this.cumulative(0.0) { element, sum -> sum + element } + +@JvmName("cumulativeSumOfInt") +fun Sequence.cumulativeSum(): Sequence = this.cumulative(0) { element, sum -> sum + element } + +@JvmName("cumulativeSumOfLong") +fun Sequence.cumulativeSum(): Sequence = this.cumulative(0L) { element, sum -> sum + element } + +fun List.cumulativeSum(space: Space): List = with(space) { + cumulative(zero) { element: T, sum: T -> sum + element } +} + +@JvmName("cumulativeSumOfDouble") +fun List.cumulativeSum(): List = this.cumulative(0.0) { element, sum -> sum + element } + +@JvmName("cumulativeSumOfInt") +fun List.cumulativeSum(): List = this.cumulative(0) { element, sum -> sum + element } + +@JvmName("cumulativeSumOfLong") +fun List.cumulativeSum(): List = this.cumulative(0L) { element, sum -> sum + element } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt similarity index 74% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 12a45615a..f18bde597 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -1,31 +1,31 @@ -package kscience.kmath.operations +package scientifik.kmath.operations /** * Stub for DSL the [Algebra] is. */ @DslMarker -public annotation class KMathContext +annotation class KMathContext /** * Represents an algebraic structure. * * @param T the type of element of this structure. */ -public interface Algebra { +interface Algebra { /** * Wrap raw string or variable */ - public fun symbol(value: String): T = error("Wrapping of '$value' is not supported in $this") + fun symbol(value: String): T = error("Wrapping of '$value' is not supported in $this") /** * Dynamic call of unary operation with name [operation] on [arg] */ - public fun unaryOperation(operation: String, arg: T): T + fun unaryOperation(operation: String, arg: T): T /** * Dynamic call of binary operation [operation] on [left] and [right] */ - public fun binaryOperation(operation: String, left: T, right: T): T + fun binaryOperation(operation: String, left: T, right: T): T } /** @@ -33,30 +33,29 @@ public interface Algebra { * * @param T the type of element of this structure. */ -public interface NumericAlgebra : Algebra { +interface NumericAlgebra : Algebra { /** * Wraps a number. */ - public fun number(value: Number): T + fun number(value: Number): T /** * Dynamic call of binary operation [operation] on [left] and [right] where left element is [Number]. */ - public fun leftSideNumberOperation(operation: String, left: Number, right: T): T = + fun leftSideNumberOperation(operation: String, left: Number, right: T): T = binaryOperation(operation, number(left), right) /** * Dynamic call of binary operation [operation] on [left] and [right] where right element is [Number]. */ - public fun rightSideNumberOperation(operation: String, left: T, right: Number): T = + fun rightSideNumberOperation(operation: String, left: T, right: Number): T = leftSideNumberOperation(operation, right, left) } /** * Call a block with an [Algebra] as receiver. */ -// TODO add contract when KT-32313 is fixed -public inline operator fun , R> A.invoke(block: A.() -> R): R = block() +inline operator fun , R> A.invoke(block: A.() -> R): R = run(block) /** * Represents "semispace", i.e. algebraic structure with associative binary operation called "addition" as well as @@ -64,7 +63,7 @@ public inline operator fun , R> A.invoke(block: A.() -> R): R = b * * @param T the type of element of this semispace. */ -public interface SpaceOperations : Algebra { +interface SpaceOperations : Algebra { /** * Addition of two elements. * @@ -72,7 +71,7 @@ public interface SpaceOperations : Algebra { * @param b the augend. * @return the sum. */ - public fun add(a: T, b: T): T + fun add(a: T, b: T): T /** * Multiplication of element by scalar. @@ -81,7 +80,7 @@ public interface SpaceOperations : Algebra { * @param k the multiplicand. * @return the produce. */ - public fun multiply(a: T, k: Number): T + fun multiply(a: T, k: Number): T // Operations to be performed in this context. Could be moved to extensions in case of KEEP-176 @@ -91,7 +90,7 @@ public interface SpaceOperations : Algebra { * @receiver this value. * @return the additive inverse of this value. */ - public operator fun T.unaryMinus(): T = multiply(this, -1.0) + operator fun T.unaryMinus(): T = multiply(this, -1.0) /** * Returns this value. @@ -99,7 +98,7 @@ public interface SpaceOperations : Algebra { * @receiver this value. * @return this value. */ - public operator fun T.unaryPlus(): T = this + operator fun T.unaryPlus(): T = this /** * Addition of two elements. @@ -108,7 +107,7 @@ public interface SpaceOperations : Algebra { * @param b the augend. * @return the sum. */ - public operator fun T.plus(b: T): T = add(this, b) + operator fun T.plus(b: T): T = add(this, b) /** * Subtraction of two elements. @@ -117,7 +116,7 @@ public interface SpaceOperations : Algebra { * @param b the subtrahend. * @return the difference. */ - public operator fun T.minus(b: T): T = add(this, -b) + operator fun T.minus(b: T): T = add(this, -b) /** * Multiplication of this element by a scalar. @@ -126,7 +125,7 @@ public interface SpaceOperations : Algebra { * @param k the multiplicand. * @return the product. */ - public operator fun T.times(k: Number): T = multiply(this, k.toDouble()) + operator fun T.times(k: Number): T = multiply(this, k.toDouble()) /** * Division of this element by scalar. @@ -135,7 +134,7 @@ public interface SpaceOperations : Algebra { * @param k the divisor. * @return the quotient. */ - public operator fun T.div(k: Number): T = multiply(this, 1.0 / k.toDouble()) + operator fun T.div(k: Number): T = multiply(this, 1.0 / k.toDouble()) /** * Multiplication of this number by element. @@ -144,7 +143,7 @@ public interface SpaceOperations : Algebra { * @param b the multiplicand. * @return the product. */ - public operator fun Number.times(b: T): T = b * this + operator fun Number.times(b: T): T = b * this override fun unaryOperation(operation: String, arg: T): T = when (operation) { PLUS_OPERATION -> arg @@ -158,16 +157,18 @@ public interface SpaceOperations : Algebra { else -> error("Binary operation $operation not defined in $this") } - public companion object { + companion object { /** * The identifier of addition. */ - public const val PLUS_OPERATION: String = "+" + const val PLUS_OPERATION: String = "+" /** * The identifier of subtraction (and negation). */ - public const val MINUS_OPERATION: String = "-" + const val MINUS_OPERATION: String = "-" + + const val NOT_OPERATION: String = "!" } } @@ -177,11 +178,11 @@ public interface SpaceOperations : Algebra { * * @param T the type of element of this group. */ -public interface Space : SpaceOperations { +interface Space : SpaceOperations { /** * The neutral element of addition. */ - public val zero: T + val zero: T } /** @@ -190,14 +191,14 @@ public interface Space : SpaceOperations { * * @param T the type of element of this semiring. */ -public interface RingOperations : SpaceOperations { +interface RingOperations : SpaceOperations { /** * Multiplies two elements. * * @param a the multiplier. * @param b the multiplicand. */ - public fun multiply(a: T, b: T): T + fun multiply(a: T, b: T): T /** * Multiplies this element by scalar. @@ -205,18 +206,18 @@ public interface RingOperations : SpaceOperations { * @receiver the multiplier. * @param b the multiplicand. */ - public operator fun T.times(b: T): T = multiply(this, b) + operator fun T.times(b: T): T = multiply(this, b) override fun binaryOperation(operation: String, left: T, right: T): T = when (operation) { TIMES_OPERATION -> multiply(left, right) else -> super.binaryOperation(operation, left, right) } - public companion object { + companion object { /** * The identifier of multiplication. */ - public const val TIMES_OPERATION: String = "*" + const val TIMES_OPERATION: String = "*" } } @@ -226,11 +227,11 @@ public interface RingOperations : SpaceOperations { * * @param T the type of element of this ring. */ -public interface Ring : Space, RingOperations, NumericAlgebra { +interface Ring : Space, RingOperations, NumericAlgebra { /** * neutral operation for multiplication */ - public val one: T + val one: T override fun number(value: Number): T = one * value.toDouble() @@ -254,7 +255,7 @@ public interface Ring : Space, RingOperations, NumericAlgebra { * @receiver the addend. * @param b the augend. */ - public operator fun T.plus(b: Number): T = this + number(b) + operator fun T.plus(b: Number): T = this + number(b) /** * Addition of scalar and element. @@ -262,7 +263,7 @@ public interface Ring : Space, RingOperations, NumericAlgebra { * @receiver the addend. * @param b the augend. */ - public operator fun Number.plus(b: T): T = b + this + operator fun Number.plus(b: T): T = b + this /** * Subtraction of element from number. @@ -271,7 +272,7 @@ public interface Ring : Space, RingOperations, NumericAlgebra { * @param b the subtrahend. * @receiver the difference. */ - public operator fun T.minus(b: Number): T = this - number(b) + operator fun T.minus(b: Number): T = this - number(b) /** * Subtraction of number from element. @@ -280,7 +281,7 @@ public interface Ring : Space, RingOperations, NumericAlgebra { * @param b the subtrahend. * @receiver the difference. */ - public operator fun Number.minus(b: T): T = -b + this + operator fun Number.minus(b: T): T = -b + this } /** @@ -289,7 +290,7 @@ public interface Ring : Space, RingOperations, NumericAlgebra { * * @param T the type of element of this semifield. */ -public interface FieldOperations : RingOperations { +interface FieldOperations : RingOperations { /** * Division of two elements. * @@ -297,7 +298,7 @@ public interface FieldOperations : RingOperations { * @param b the divisor. * @return the quotient. */ - public fun divide(a: T, b: T): T + fun divide(a: T, b: T): T /** * Division of two elements. @@ -306,18 +307,18 @@ public interface FieldOperations : RingOperations { * @param b the divisor. * @return the quotient. */ - public operator fun T.div(b: T): T = divide(this, b) + operator fun T.div(b: T): T = divide(this, b) override fun binaryOperation(operation: String, left: T, right: T): T = when (operation) { DIV_OPERATION -> divide(left, right) else -> super.binaryOperation(operation, left, right) } - public companion object { + companion object { /** * The identifier of division. */ - public const val DIV_OPERATION: String = "/" + const val DIV_OPERATION: String = "/" } } @@ -327,7 +328,7 @@ public interface FieldOperations : RingOperations { * * @param T the type of element of this semifield. */ -public interface Field : Ring, FieldOperations { +interface Field : Ring, FieldOperations { /** * Division of element by scalar. * @@ -335,5 +336,5 @@ public interface Field : Ring, FieldOperations { * @param b the divisor. * @return the quotient. */ - public operator fun Number.div(b: T): T = this * divide(one, b) + operator fun Number.div(b: T): T = this * divide(one, b) } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/AlgebraElements.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt similarity index 59% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/operations/AlgebraElements.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt index aa572d894..197897c14 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/AlgebraElements.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt @@ -1,15 +1,15 @@ -package kscience.kmath.operations +package scientifik.kmath.operations /** * The generic mathematics elements which is able to store its context * * @param C the type of mathematical context for this element. */ -public interface MathElement { +interface MathElement { /** * The context this element belongs to. */ - public val context: C + val context: C } /** @@ -18,16 +18,16 @@ public interface MathElement { * @param T the type wrapped by this wrapper. * @param I the type of this wrapper. */ -public interface MathWrapper { +interface MathWrapper { /** * Unwraps [I] to [T]. */ - public fun unwrap(): T + fun unwrap(): T /** * Wraps [T] to [I]. */ - public fun T.wrap(): I + fun T.wrap(): I } /** @@ -37,14 +37,14 @@ public interface MathWrapper { * @param I self type of the element. Needed for static type checking. * @param S the type of space. */ -public interface SpaceElement, S : Space> : MathElement, MathWrapper { +interface SpaceElement, S : Space> : MathElement, MathWrapper { /** * Adds element to this one. * * @param b the augend. * @return the sum. */ - public operator fun plus(b: T): I = context.add(unwrap(), b).wrap() + operator fun plus(b: T): I = context.add(unwrap(), b).wrap() /** * Subtracts element from this one. @@ -52,7 +52,7 @@ public interface SpaceElement, S : Space> : Math * @param b the subtrahend. * @return the difference. */ - public operator fun minus(b: T): I = context.add(unwrap(), context.multiply(b, -1.0)).wrap() + operator fun minus(b: T): I = context.add(unwrap(), context.multiply(b, -1.0)).wrap() /** * Multiplies this element by number. @@ -60,7 +60,7 @@ public interface SpaceElement, S : Space> : Math * @param k the multiplicand. * @return the product. */ - public operator fun times(k: Number): I = context.multiply(unwrap(), k.toDouble()).wrap() + operator fun times(k: Number): I = context.multiply(unwrap(), k.toDouble()).wrap() /** * Divides this element by number. @@ -68,34 +68,34 @@ public interface SpaceElement, S : Space> : Math * @param k the divisor. * @return the quotient. */ - public operator fun div(k: Number): I = context.multiply(unwrap(), 1.0 / k.toDouble()).wrap() + operator fun div(k: Number): I = context.multiply(unwrap(), 1.0 / k.toDouble()).wrap() } /** * The element of [Ring]. * - * @param T the type of ring operation results. + * @param T the type of space operation results. * @param I self type of the element. Needed for static type checking. - * @param R the type of ring. + * @param R the type of space. */ -public interface RingElement, R : Ring> : SpaceElement { +interface RingElement, R : Ring> : SpaceElement { /** * Multiplies this element by another one. * * @param b the multiplicand. * @return the product. */ - public operator fun times(b: T): I = context.multiply(unwrap(), b).wrap() + operator fun times(b: T): I = context.multiply(unwrap(), b).wrap() } /** * The element of [Field]. * - * @param T the type of field operation results. + * @param T the type of space operation results. * @param I self type of the element. Needed for static type checking. * @param F the type of field. */ -public interface FieldElement, F : Field> : RingElement { +interface FieldElement, F : Field> : RingElement { override val context: F /** @@ -104,5 +104,5 @@ public interface FieldElement, F : Field> : Ring * @param b the divisor. * @return the quotient. */ - public operator fun div(b: T): I = context.divide(unwrap(), b).wrap() + operator fun div(b: T): I = context.divide(unwrap(), b).wrap() } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/AlgebraExtensions.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt similarity index 69% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/operations/AlgebraExtensions.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt index 4527a2a42..00b16dc98 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/AlgebraExtensions.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt @@ -1,4 +1,4 @@ -package kscience.kmath.operations +package scientifik.kmath.operations /** * Returns the sum of all elements in the iterable in this [Space]. @@ -7,7 +7,7 @@ package kscience.kmath.operations * @param data the iterable to sum up. * @return the sum. */ -public fun Space.sum(data: Iterable): T = data.fold(zero) { left, right -> add(left, right) } +fun Space.sum(data: Iterable): T = data.fold(zero) { left, right -> add(left, right) } /** * Returns the sum of all elements in the sequence in this [Space]. @@ -16,7 +16,7 @@ public fun Space.sum(data: Iterable): T = data.fold(zero) { left, righ * @param data the sequence to sum up. * @return the sum. */ -public fun Space.sum(data: Sequence): T = data.fold(zero) { left, right -> add(left, right) } +fun Space.sum(data: Sequence): T = data.fold(zero) { left, right -> add(left, right) } /** * Returns an average value of elements in the iterable in this [Space]. @@ -24,9 +24,8 @@ public fun Space.sum(data: Sequence): T = data.fold(zero) { left, righ * @receiver the algebra that provides addition and division. * @param data the iterable to find average. * @return the average value. - * @author Iaroslav Postovalov */ -public fun Space.average(data: Iterable): T = sum(data) / data.count() +fun Space.average(data: Iterable): T = sum(data) / data.count() /** * Returns an average value of elements in the sequence in this [Space]. @@ -34,14 +33,8 @@ public fun Space.average(data: Iterable): T = sum(data) / data.count() * @receiver the algebra that provides addition and division. * @param data the sequence to find average. * @return the average value. - * @author Iaroslav Postovalov */ -public fun Space.average(data: Sequence): T = sum(data) / data.count() - -/** - * Absolute of the comparable [value] - */ -public fun > Space.abs(value: T): T = if (value > zero) value else -value +fun Space.average(data: Sequence): T = sum(data) / data.count() /** * Returns the sum of all elements in the iterable in provided space. @@ -50,7 +43,7 @@ public fun > Space.abs(value: T): T = if (value > zero) val * @param space the algebra that provides addition. * @return the sum. */ -public fun Iterable.sumWith(space: Space): T = space.sum(this) +fun Iterable.sumWith(space: Space): T = space.sum(this) /** * Returns the sum of all elements in the sequence in provided space. @@ -59,7 +52,7 @@ public fun Iterable.sumWith(space: Space): T = space.sum(this) * @param space the algebra that provides addition. * @return the sum. */ -public fun Sequence.sumWith(space: Space): T = space.sum(this) +fun Sequence.sumWith(space: Space): T = space.sum(this) /** * Returns an average value of elements in the iterable in this [Space]. @@ -67,9 +60,8 @@ public fun Sequence.sumWith(space: Space): T = space.sum(this) * @receiver the iterable to find average. * @param space the algebra that provides addition and division. * @return the average value. - * @author Iaroslav Postovalov */ -public fun Iterable.averageWith(space: Space): T = space.average(this) +fun Iterable.averageWith(space: Space): T = space.average(this) /** * Returns an average value of elements in the sequence in this [Space]. @@ -77,9 +69,8 @@ public fun Iterable.averageWith(space: Space): T = space.average(this) * @receiver the sequence to find average. * @param space the algebra that provides addition and division. * @return the average value. - * @author Iaroslav Postovalov */ -public fun Sequence.averageWith(space: Space): T = space.average(this) +fun Sequence.averageWith(space: Space): T = space.average(this) //TODO optimized power operation @@ -91,7 +82,7 @@ public fun Sequence.averageWith(space: Space): T = space.average(this) * @param power the exponent. * @return the base raised to the power. */ -public fun Ring.power(arg: T, power: Int): T { +fun Ring.power(arg: T, power: Int): T { require(power >= 0) { "The power can't be negative." } require(power != 0 || arg != zero) { "The $zero raised to $power is not defined." } if (power == 0) return one @@ -107,9 +98,8 @@ public fun Ring.power(arg: T, power: Int): T { * @param arg the base. * @param power the exponent. * @return the base raised to the power. - * @author Iaroslav Postovalov */ -public fun Field.power(arg: T, power: Int): T { +fun Field.power(arg: T, power: Int): T { require(power != 0 || arg != zero) { "The $zero raised to $power is not defined." } if (power == 0) return one if (power < 0) return one / (this as Ring).power(arg, -power) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/BigInt.kt similarity index 57% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/operations/BigInt.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/BigInt.kt index 20f289596..07163750b 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/BigInt.kt @@ -1,22 +1,23 @@ -package kscience.kmath.operations +package scientifik.kmath.operations -import kscience.kmath.operations.BigInt.Companion.BASE -import kscience.kmath.operations.BigInt.Companion.BASE_SIZE -import kscience.kmath.structures.* +import scientifik.kmath.operations.BigInt.Companion.BASE +import scientifik.kmath.operations.BigInt.Companion.BASE_SIZE +import scientifik.kmath.structures.* import kotlin.math.log2 import kotlin.math.max import kotlin.math.min import kotlin.math.sign -public typealias Magnitude = UIntArray -public typealias TBase = ULong + +typealias Magnitude = UIntArray +typealias TBase = ULong /** * Kotlin Multiplatform implementation of Big Integer numbers (KBigInteger). * * @author Robert Drynkin (https://github.com/robdrynkin) and Peter Klimai (https://github.com/pklimai) */ -public object BigIntField : Field { +object BigIntField : Field { override val zero: BigInt = BigInt.ZERO override val one: BigInt = BigInt.ONE @@ -27,92 +28,113 @@ public object BigIntField : Field { override fun multiply(a: BigInt, b: BigInt): BigInt = a.times(b) - public operator fun String.unaryPlus(): BigInt = this.parseBigInteger() ?: error("Can't parse $this as big integer") + operator fun String.unaryPlus(): BigInt = this.parseBigInteger() ?: error("Can't parse $this as big integer") - public operator fun String.unaryMinus(): BigInt = + operator fun String.unaryMinus(): BigInt = -(this.parseBigInteger() ?: error("Can't parse $this as big integer")) override fun divide(a: BigInt, b: BigInt): BigInt = a.div(b) } -public class BigInt internal constructor( +class BigInt internal constructor( private val sign: Byte, private val magnitude: Magnitude ) : Comparable { - public override fun compareTo(other: BigInt): Int = when { - (sign == 0.toByte()) and (other.sign == 0.toByte()) -> 0 - sign < other.sign -> -1 - sign > other.sign -> 1 - else -> sign * compareMagnitudes(magnitude, other.magnitude) - } - public override fun equals(other: Any?): Boolean = - if (other is BigInt) compareTo(other) == 0 else error("Can't compare KBigInteger to a different type") - - public override fun hashCode(): Int = magnitude.hashCode() + sign - - public fun abs(): BigInt = if (sign == 0.toByte()) this else BigInt(1, magnitude) - - public operator fun unaryMinus(): BigInt = - if (this.sign == 0.toByte()) this else BigInt((-this.sign).toByte(), this.magnitude) - - public operator fun plus(b: BigInt): BigInt = when { - b.sign == 0.toByte() -> this - sign == 0.toByte() -> b - this == -b -> ZERO - sign == b.sign -> BigInt(sign, addMagnitudes(magnitude, b.magnitude)) - - else -> { - val comp = compareMagnitudes(magnitude, b.magnitude) - - if (comp == 1) - BigInt(sign, subtractMagnitudes(magnitude, b.magnitude)) - else - BigInt((-sign).toByte(), subtractMagnitudes(b.magnitude, magnitude)) + override fun compareTo(other: BigInt): Int { + return when { + (this.sign == 0.toByte()) and (other.sign == 0.toByte()) -> 0 + this.sign < other.sign -> -1 + this.sign > other.sign -> 1 + else -> this.sign * compareMagnitudes(this.magnitude, other.magnitude) } } - public operator fun minus(b: BigInt): BigInt = this + (-b) + override fun equals(other: Any?): Boolean { + if (other is BigInt) { + return this.compareTo(other) == 0 + } else error("Can't compare KBigInteger to a different type") + } - public operator fun times(b: BigInt): BigInt = when { - this.sign == 0.toByte() -> ZERO - b.sign == 0.toByte() -> ZERO + override fun hashCode(): Int { + return magnitude.hashCode() + this.sign + } + + fun abs(): BigInt = if (sign == 0.toByte()) this else BigInt(1, magnitude) + + operator fun unaryMinus(): BigInt { + return if (this.sign == 0.toByte()) this else BigInt((-this.sign).toByte(), this.magnitude) + } + + operator fun plus(b: BigInt): BigInt { + return when { + b.sign == 0.toByte() -> this + this.sign == 0.toByte() -> b + this == -b -> ZERO + this.sign == b.sign -> BigInt(this.sign, addMagnitudes(this.magnitude, b.magnitude)) + else -> { + val comp: Int = compareMagnitudes(this.magnitude, b.magnitude) + + if (comp == 1) { + BigInt(this.sign, subtractMagnitudes(this.magnitude, b.magnitude)) + } else { + BigInt((-this.sign).toByte(), subtractMagnitudes(b.magnitude, this.magnitude)) + } + } + } + } + + operator fun minus(b: BigInt): BigInt { + return this + (-b) + } + + operator fun times(b: BigInt): BigInt { + return when { + this.sign == 0.toByte() -> ZERO + b.sign == 0.toByte() -> ZERO // TODO: Karatsuba - else -> BigInt((this.sign * b.sign).toByte(), multiplyMagnitudes(this.magnitude, b.magnitude)) + else -> BigInt((this.sign * b.sign).toByte(), multiplyMagnitudes(this.magnitude, b.magnitude)) + } } - public operator fun times(other: UInt): BigInt = when { - sign == 0.toByte() -> ZERO - other == 0U -> ZERO - else -> BigInt(sign, multiplyMagnitudeByUInt(magnitude, other)) + operator fun times(other: UInt): BigInt { + return when { + this.sign == 0.toByte() -> ZERO + other == 0U -> ZERO + else -> BigInt(this.sign, multiplyMagnitudeByUInt(this.magnitude, other)) + } } - public operator fun times(other: Int): BigInt = if (other > 0) - this * kotlin.math.abs(other).toUInt() - else - -this * kotlin.math.abs(other).toUInt() + operator fun times(other: Int): BigInt { + return if (other > 0) + this * kotlin.math.abs(other).toUInt() + else + -this * kotlin.math.abs(other).toUInt() + } - public operator fun div(other: UInt): BigInt = BigInt(this.sign, divideMagnitudeByUInt(this.magnitude, other)) + operator fun div(other: UInt): BigInt { + return BigInt(this.sign, divideMagnitudeByUInt(this.magnitude, other)) + } - public operator fun div(other: Int): BigInt = BigInt( - (this.sign * other.sign).toByte(), - divideMagnitudeByUInt(this.magnitude, kotlin.math.abs(other).toUInt()) - ) + operator fun div(other: Int): BigInt { + return BigInt( + (this.sign * other.sign).toByte(), + divideMagnitudeByUInt(this.magnitude, kotlin.math.abs(other).toUInt()) + ) + } private fun division(other: BigInt): Pair { // Long division algorithm: // https://en.wikipedia.org/wiki/Division_algorithm#Integer_division_(unsigned)_with_remainder // TODO: Implement more effective algorithm - var q = ZERO - var r = ZERO + var q: BigInt = ZERO + var r: BigInt = ZERO val bitSize = (BASE_SIZE * (this.magnitude.size - 1) + log2(this.magnitude.lastOrNull()?.toFloat() ?: 0f + 1)).toInt() - for (i in bitSize downTo 0) { r = r shl 1 r = r or ((abs(this) shr i) and ONE) - if (r >= abs(other)) { r -= abs(other) q += (ONE shl i) @@ -122,84 +144,99 @@ public class BigInt internal constructor( return Pair(BigInt((this.sign * other.sign).toByte(), q.magnitude), r) } - public operator fun div(other: BigInt): BigInt = division(other).first + operator fun div(other: BigInt): BigInt { + return this.division(other).first + } - public infix fun shl(i: Int): BigInt { + infix fun shl(i: Int): BigInt { if (this == ZERO) return ZERO if (i == 0) return this + val fullShifts = i / BASE_SIZE + 1 val relShift = i % BASE_SIZE val shiftLeft = { x: UInt -> if (relShift >= 32) 0U else x shl relShift } val shiftRight = { x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shr (BASE_SIZE - relShift) } - val newMagnitude = Magnitude(magnitude.size + fullShifts) - for (j in magnitude.indices) { + val newMagnitude: Magnitude = Magnitude(this.magnitude.size + fullShifts) + + for (j in this.magnitude.indices) { newMagnitude[j + fullShifts - 1] = shiftLeft(this.magnitude[j]) - - if (j != 0) + if (j != 0) { newMagnitude[j + fullShifts - 1] = newMagnitude[j + fullShifts - 1] or shiftRight(this.magnitude[j - 1]) + } } - newMagnitude[magnitude.size + fullShifts - 1] = shiftRight(magnitude.last()) + newMagnitude[this.magnitude.size + fullShifts - 1] = shiftRight(this.magnitude.last()) + return BigInt(this.sign, stripLeadingZeros(newMagnitude)) } - public infix fun shr(i: Int): BigInt { + infix fun shr(i: Int): BigInt { if (this == ZERO) return ZERO if (i == 0) return this + val fullShifts = i / BASE_SIZE val relShift = i % BASE_SIZE val shiftRight = { x: UInt -> if (relShift >= 32) 0U else x shr relShift } val shiftLeft = { x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shl (BASE_SIZE - relShift) } - if (this.magnitude.size - fullShifts <= 0) return ZERO - val newMagnitude: Magnitude = Magnitude(magnitude.size - fullShifts) + if (this.magnitude.size - fullShifts <= 0) { + return ZERO + } + val newMagnitude: Magnitude = Magnitude(this.magnitude.size - fullShifts) - for (j in fullShifts until magnitude.size) { - newMagnitude[j - fullShifts] = shiftRight(magnitude[j]) - - if (j != magnitude.size - 1) - newMagnitude[j - fullShifts] = newMagnitude[j - fullShifts] or shiftLeft(magnitude[j + 1]) + for (j in fullShifts until this.magnitude.size) { + newMagnitude[j - fullShifts] = shiftRight(this.magnitude[j]) + if (j != this.magnitude.size - 1) { + newMagnitude[j - fullShifts] = newMagnitude[j - fullShifts] or shiftLeft(this.magnitude[j + 1]) + } } return BigInt(this.sign, stripLeadingZeros(newMagnitude)) } - public infix fun or(other: BigInt): BigInt { + infix fun or(other: BigInt): BigInt { if (this == ZERO) return other if (other == ZERO) return this - val resSize = max(magnitude.size, other.magnitude.size) + val resSize = max(this.magnitude.size, other.magnitude.size) val newMagnitude: Magnitude = Magnitude(resSize) - for (i in 0 until resSize) { - if (i < magnitude.size) newMagnitude[i] = newMagnitude[i] or magnitude[i] - if (i < other.magnitude.size) newMagnitude[i] = newMagnitude[i] or other.magnitude[i] + if (i < this.magnitude.size) { + newMagnitude[i] = newMagnitude[i] or this.magnitude[i] + } + if (i < other.magnitude.size) { + newMagnitude[i] = newMagnitude[i] or other.magnitude[i] + } } - return BigInt(1, stripLeadingZeros(newMagnitude)) } - public infix fun and(other: BigInt): BigInt { + infix fun and(other: BigInt): BigInt { if ((this == ZERO) or (other == ZERO)) return ZERO val resSize = min(this.magnitude.size, other.magnitude.size) val newMagnitude: Magnitude = Magnitude(resSize) - for (i in 0 until resSize) newMagnitude[i] = this.magnitude[i] and other.magnitude[i] + for (i in 0 until resSize) { + newMagnitude[i] = this.magnitude[i] and other.magnitude[i] + } return BigInt(1, stripLeadingZeros(newMagnitude)) } - public operator fun rem(other: Int): Int { + operator fun rem(other: Int): Int { val res = this - (this / other) * other return if (res == ZERO) 0 else res.sign * res.magnitude[0].toInt() } - public operator fun rem(other: BigInt): BigInt = this - (this / other) * other + operator fun rem(other: BigInt): BigInt { + return this - (this / other) * other + } - public fun modPow(exponent: BigInt, m: BigInt): BigInt = when { - exponent == ZERO -> ONE - exponent % 2 == 1 -> (this * modPow(exponent - ONE, m)) % m - - else -> { - val sqRoot = modPow(exponent / 2, m) - (sqRoot * sqRoot) % m + fun modPow(exponent: BigInt, m: BigInt): BigInt { + return when { + exponent == ZERO -> ONE + exponent % 2 == 1 -> (this * modPow(exponent - ONE, m)) % m + else -> { + val sqRoot = modPow(exponent / 2, m) + (sqRoot * sqRoot) % m + } } } @@ -223,11 +260,11 @@ public class BigInt internal constructor( return res } - public companion object { - public const val BASE: ULong = 0xffffffffUL - public const val BASE_SIZE: Int = 32 - public val ZERO: BigInt = BigInt(0, uintArrayOf()) - public val ONE: BigInt = BigInt(1, uintArrayOf(1u)) + companion object { + const val BASE: ULong = 0xffffffffUL + const val BASE_SIZE: Int = 32 + val ZERO: BigInt = BigInt(0, uintArrayOf()) + val ONE: BigInt = BigInt(1, uintArrayOf(1u)) private val hexMapping: HashMap = hashMapOf( 0U to "0", 1U to "1", 2U to "2", 3U to "3", @@ -254,9 +291,9 @@ public class BigInt internal constructor( } private fun addMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { - val resultLength = max(mag1.size, mag2.size) + 1 + val resultLength: Int = max(mag1.size, mag2.size) + 1 val result = Magnitude(resultLength) - var carry = 0uL + var carry: TBase = 0UL for (i in 0 until resultLength - 1) { val res = when { @@ -264,22 +301,20 @@ public class BigInt internal constructor( i >= mag2.size -> mag1[i].toULong() + carry else -> mag1[i].toULong() + mag2[i].toULong() + carry } - result[i] = (res and BASE).toUInt() carry = (res shr BASE_SIZE) } - result[resultLength - 1] = carry.toUInt() return stripLeadingZeros(result) } private fun subtractMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { - val resultLength = mag1.size + val resultLength: Int = mag1.size val result = Magnitude(resultLength) var carry = 0L for (i in 0 until resultLength) { - var res = + var res: Long = if (i < mag2.size) mag1[i].toLong() - mag2[i].toLong() - carry else mag1[i].toLong() - carry @@ -293,13 +328,13 @@ public class BigInt internal constructor( } private fun multiplyMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { - val resultLength = mag.size + 1 + val resultLength: Int = mag.size + 1 val result = Magnitude(resultLength) - var carry = 0uL + var carry: ULong = 0UL for (i in mag.indices) { val cur: ULong = carry + mag[i].toULong() * x.toULong() - result[i] = (cur and BASE).toUInt() + result[i] = (cur and BASE.toULong()).toUInt() carry = cur shr BASE_SIZE } result[resultLength - 1] = (carry and BASE).toUInt() @@ -308,18 +343,16 @@ public class BigInt internal constructor( } private fun multiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { - val resultLength = mag1.size + mag2.size + val resultLength: Int = mag1.size + mag2.size val result = Magnitude(resultLength) for (i in mag1.indices) { - var carry = 0uL - + var carry: ULong = 0UL for (j in mag2.indices) { val cur: ULong = result[i + j].toULong() + mag1[i].toULong() * mag2[j].toULong() + carry - result[i + j] = (cur and BASE).toUInt() + result[i + j] = (cur and BASE.toULong()).toUInt() carry = cur shr BASE_SIZE } - result[i + mag2.size] = (carry and BASE).toUInt() } @@ -327,46 +360,48 @@ public class BigInt internal constructor( } private fun divideMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { - val resultLength = mag.size + val resultLength: Int = mag.size val result = Magnitude(resultLength) - var carry = 0uL + var carry: ULong = 0UL for (i in mag.size - 1 downTo 0) { val cur: ULong = mag[i].toULong() + (carry shl BASE_SIZE) result[i] = (cur / x).toUInt() carry = cur % x } - return stripLeadingZeros(result) } + } + } -private fun stripLeadingZeros(mag: Magnitude): Magnitude { - if (mag.isEmpty() || mag.last() != 0U) return mag - var resSize = mag.size - 1 +private fun stripLeadingZeros(mag: Magnitude): Magnitude { + if (mag.isEmpty() || mag.last() != 0U) { + return mag + } + var resSize: Int = mag.size - 1 while (mag[resSize] == 0U) { - if (resSize == 0) break + if (resSize == 0) + break resSize -= 1 } - return mag.sliceArray(IntRange(0, resSize)) } -public fun abs(x: BigInt): BigInt = x.abs() +fun abs(x: BigInt): BigInt = x.abs() /** * Convert this [Int] to [BigInt] */ -public fun Int.toBigInt(): BigInt = BigInt(sign.toByte(), uintArrayOf(kotlin.math.abs(this).toUInt())) +fun Int.toBigInt(): BigInt = BigInt(sign.toByte(), uintArrayOf(kotlin.math.abs(this).toUInt())) /** * Convert this [Long] to [BigInt] */ -public fun Long.toBigInt(): BigInt = BigInt( - sign.toByte(), - stripLeadingZeros( +fun Long.toBigInt(): BigInt = BigInt( + sign.toByte(), stripLeadingZeros( uintArrayOf( (kotlin.math.abs(this).toULong() and BASE).toUInt(), ((kotlin.math.abs(this).toULong() shr BASE_SIZE) and BASE).toUInt() @@ -377,12 +412,12 @@ public fun Long.toBigInt(): BigInt = BigInt( /** * Convert UInt to [BigInt] */ -public fun UInt.toBigInt(): BigInt = BigInt(1, uintArrayOf(this)) +fun UInt.toBigInt(): BigInt = BigInt(1, uintArrayOf(this)) /** * Convert ULong to [BigInt] */ -public fun ULong.toBigInt(): BigInt = BigInt( +fun ULong.toBigInt(): BigInt = BigInt( 1, stripLeadingZeros( uintArrayOf( @@ -395,12 +430,12 @@ public fun ULong.toBigInt(): BigInt = BigInt( /** * Create a [BigInt] with this array of magnitudes with protective copy */ -public fun UIntArray.toBigInt(sign: Byte): BigInt { - require(sign != 0.toByte() || !isNotEmpty()) - return BigInt(sign, copyOf()) +fun UIntArray.toBigInt(sign: Byte): BigInt { + if (sign == 0.toByte() && isNotEmpty()) error("") + return BigInt(sign, this.copyOf()) } -private val hexChToInt: MutableMap = hashMapOf( +val hexChToInt: MutableMap = hashMapOf( '0' to 0, '1' to 1, '2' to 2, '3' to 3, '4' to 4, '5' to 5, '6' to 6, '7' to 7, '8' to 8, '9' to 9, 'A' to 10, 'B' to 11, @@ -410,10 +445,9 @@ private val hexChToInt: MutableMap = hashMapOf( /** * Returns null if a valid number can not be read from a string */ -public fun String.parseBigInteger(): BigInt? { +fun String.parseBigInteger(): BigInt? { val sign: Int val sPositive: String - when { this[0] == '+' -> { sign = +1 @@ -428,42 +462,40 @@ public fun String.parseBigInteger(): BigInt? { sign = +1 } } - var res = BigInt.ZERO var digitValue = BigInt.ONE val sPositiveUpper = sPositive.toUpperCase() - if (sPositiveUpper.startsWith("0X")) { // hex representation val sHex = sPositiveUpper.substring(2) - for (ch in sHex.reversed()) { if (ch == '_') continue res += digitValue * (hexChToInt[ch] ?: return null) digitValue *= 16.toBigInt() } - } else for (ch in sPositiveUpper.reversed()) { - // decimal representation - if (ch == '_') continue - if (ch !in '0'..'9') { - return null + } else { // decimal representation + for (ch in sPositiveUpper.reversed()) { + if (ch == '_') continue + if (ch !in '0'..'9') { + return null + } + res += digitValue * (ch.toInt() - '0'.toInt()) + digitValue *= 10.toBigInt() } - res += digitValue * (ch.toInt() - '0'.toInt()) - digitValue *= 10.toBigInt() } - return res * sign } -public inline fun Buffer.Companion.bigInt(size: Int, initializer: (Int) -> BigInt): Buffer = +inline fun Buffer.Companion.bigInt(size: Int, initializer: (Int) -> BigInt): Buffer = boxing(size, initializer) -public inline fun MutableBuffer.Companion.bigInt(size: Int, initializer: (Int) -> BigInt): MutableBuffer = +inline fun MutableBuffer.Companion.bigInt(size: Int, initializer: (Int) -> BigInt): MutableBuffer = boxing(size, initializer) -public fun NDAlgebra.Companion.bigInt(vararg shape: Int): BoxingNDRing = +fun NDAlgebra.Companion.bigInt(vararg shape: Int): BoxingNDRing = BoxingNDRing(shape, BigIntField, Buffer.Companion::bigInt) -public fun NDElement.Companion.bigInt( +fun NDElement.Companion.bigInt( vararg shape: Int, initializer: BigIntField.(IntArray) -> BigInt -): BufferedNDRingElement = NDAlgebra.bigInt(*shape).produce(initializer) +): BufferedNDRingElement = + NDAlgebra.bigInt(*shape).produce(initializer) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt similarity index 65% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/operations/Complex.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index 703931c7c..76df0f45d 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -1,24 +1,23 @@ -package kscience.kmath.operations +package scientifik.kmath.operations -import kscience.kmath.memory.MemoryReader -import kscience.kmath.memory.MemorySpec -import kscience.kmath.memory.MemoryWriter -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.MemoryBuffer -import kscience.kmath.structures.MutableBuffer -import kscience.kmath.structures.MutableMemoryBuffer +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.MemoryBuffer +import scientifik.kmath.structures.MutableBuffer +import scientifik.memory.MemoryReader +import scientifik.memory.MemorySpec +import scientifik.memory.MemoryWriter import kotlin.math.* /** * This complex's conjugate. */ -public val Complex.conjugate: Complex +val Complex.conjugate: Complex get() = Complex(re, -im) /** * This complex's reciprocal. */ -public val Complex.reciprocal: Complex +val Complex.reciprocal: Complex get() { val scale = re * re + im * im return Complex(re / scale, -im / scale) @@ -27,13 +26,13 @@ public val Complex.reciprocal: Complex /** * Absolute value of complex number. */ -public val Complex.r: Double +val Complex.r: Double get() = sqrt(re * re + im * im) /** * An angle between vector represented by complex number and X axis. */ -public val Complex.theta: Double +val Complex.theta: Double get() = atan(im / re) private val PI_DIV_2 = Complex(PI / 2, 0) @@ -41,14 +40,14 @@ private val PI_DIV_2 = Complex(PI / 2, 0) /** * A field of [Complex]. */ -public object ComplexField : ExtendedField, Norm { +object ComplexField : ExtendedField, Norm { override val zero: Complex = 0.0.toComplex() override val one: Complex = 1.0.toComplex() /** * The imaginary unit. */ - public val i: Complex = Complex(0.0, 1.0) + val i: Complex = Complex(0.0, 1.0) override fun add(a: Complex, b: Complex): Complex = Complex(a.re + b.re, a.im + b.im) @@ -116,7 +115,7 @@ public object ComplexField : ExtendedField, Norm { * @param c the augend. * @return the sum. */ - public operator fun Double.plus(c: Complex): Complex = add(this.toComplex(), c) + operator fun Double.plus(c: Complex): Complex = add(this.toComplex(), c) /** * Subtracts complex number from real one. @@ -125,7 +124,7 @@ public object ComplexField : ExtendedField, Norm { * @param c the subtrahend. * @return the difference. */ - public operator fun Double.minus(c: Complex): Complex = add(this.toComplex(), -c) + operator fun Double.minus(c: Complex): Complex = add(this.toComplex(), -c) /** * Adds real number to complex one. @@ -134,7 +133,7 @@ public object ComplexField : ExtendedField, Norm { * @param d the augend. * @return the sum. */ - public operator fun Complex.plus(d: Double): Complex = d + this + operator fun Complex.plus(d: Double): Complex = d + this /** * Subtracts real number from complex one. @@ -143,7 +142,7 @@ public object ComplexField : ExtendedField, Norm { * @param d the subtrahend. * @return the difference. */ - public operator fun Complex.minus(d: Double): Complex = add(this, -d.toComplex()) + operator fun Complex.minus(d: Double): Complex = add(this, -d.toComplex()) /** * Multiplies real number by complex one. @@ -152,7 +151,7 @@ public object ComplexField : ExtendedField, Norm { * @param c the multiplicand. * @receiver the product. */ - public operator fun Double.times(c: Complex): Complex = Complex(c.re * this, c.im * this) + operator fun Double.times(c: Complex): Complex = Complex(c.re * this, c.im * this) override fun norm(arg: Complex): Complex = sqrt(arg.conjugate * arg) @@ -160,14 +159,13 @@ public object ComplexField : ExtendedField, Norm { } /** - * Represents `double`-based complex number. + * Represents complex number. * * @property re The real part. * @property im The imaginary part. */ -public data class Complex(val re: Double, val im: Double) : FieldElement, - Comparable { - public constructor(re: Number, im: Number) : this(re.toDouble(), im.toDouble()) +data class Complex(val re: Double, val im: Double) : FieldElement, Comparable { + constructor(re: Number, im: Number) : this(re.toDouble(), im.toDouble()) override val context: ComplexField get() = ComplexField @@ -177,16 +175,11 @@ public data class Complex(val re: Double, val im: Double) : FieldElement { + override val objectSize: Int = 16 - - public companion object : MemorySpec { - override val objectSize: Int - get() = 16 - - override fun MemoryReader.read(offset: Int): Complex = Complex(readDouble(offset), readDouble(offset + 8)) + override fun MemoryReader.read(offset: Int): Complex = + Complex(readDouble(offset), readDouble(offset + 8)) override fun MemoryWriter.write(offset: Int, value: Complex) { writeDouble(offset, value.re) @@ -195,25 +188,18 @@ public data class Complex(val re: Double, val im: Double) : FieldElement Complex): Buffer = - MemoryBuffer.create(Complex, size, init) +inline fun Buffer.Companion.complex(size: Int, crossinline init: (Int) -> Complex): Buffer { + return MemoryBuffer.create(Complex, size, init) +} -/** - * Creates a new buffer of complex numbers with the specified [size], where each element is calculated by calling the - * specified [init] function. - */ -public inline fun MutableBuffer.Companion.complex(size: Int, init: (Int) -> Complex): MutableBuffer = - MutableMemoryBuffer.create(Complex, size, init) +inline fun MutableBuffer.Companion.complex(size: Int, crossinline init: (Int) -> Complex): Buffer { + return MemoryBuffer.create(Complex, size, init) +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt new file mode 100644 index 000000000..f5ab96b63 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt @@ -0,0 +1,281 @@ +package scientifik.kmath.operations + +import kotlin.math.abs +import kotlin.math.pow as kpow + +/** + * Advanced Number-like semifield that implements basic operations. + */ +interface ExtendedFieldOperations : + FieldOperations, + TrigonometricOperations, + HyperbolicOperations, + PowerOperations, + ExponentialOperations { + + override fun tan(arg: T): T = sin(arg) / cos(arg) + override fun tanh(arg: T): T = sinh(arg) / cosh(arg) + + override fun unaryOperation(operation: String, arg: T): T = when (operation) { + TrigonometricOperations.COS_OPERATION -> cos(arg) + TrigonometricOperations.SIN_OPERATION -> sin(arg) + TrigonometricOperations.TAN_OPERATION -> tan(arg) + TrigonometricOperations.ACOS_OPERATION -> acos(arg) + TrigonometricOperations.ASIN_OPERATION -> asin(arg) + TrigonometricOperations.ATAN_OPERATION -> atan(arg) + HyperbolicOperations.COSH_OPERATION -> cosh(arg) + HyperbolicOperations.SINH_OPERATION -> sinh(arg) + HyperbolicOperations.TANH_OPERATION -> tanh(arg) + HyperbolicOperations.ACOSH_OPERATION -> acosh(arg) + HyperbolicOperations.ASINH_OPERATION -> asinh(arg) + HyperbolicOperations.ATANH_OPERATION -> atanh(arg) + PowerOperations.SQRT_OPERATION -> sqrt(arg) + ExponentialOperations.EXP_OPERATION -> exp(arg) + ExponentialOperations.LN_OPERATION -> ln(arg) + else -> super.unaryOperation(operation, arg) + } +} + + +/** + * Advanced Number-like field that implements basic operations. + */ +interface ExtendedField : ExtendedFieldOperations, Field { + override fun sinh(arg: T): T = (exp(arg) - exp(-arg)) / 2 + override fun cosh(arg: T): T = (exp(arg) + exp(-arg)) / 2 + override fun tanh(arg: T): T = (exp(arg) - exp(-arg)) / (exp(-arg) + exp(arg)) + override fun asinh(arg: T): T = ln(sqrt(arg * arg + one) + arg) + override fun acosh(arg: T): T = ln(arg + sqrt((arg - one) * (arg + one))) + override fun atanh(arg: T): T = (ln(arg + one) - ln(one - arg)) / 2 + + override fun rightSideNumberOperation(operation: String, left: T, right: Number): T = when (operation) { + PowerOperations.POW_OPERATION -> power(left, right) + else -> super.rightSideNumberOperation(operation, left, right) + } +} + +/** + * Real field element wrapping double. + * + * @property value the [Double] value wrapped by this [Real]. + * + * TODO inline does not work due to compiler bug. Waiting for fix for KT-27586 + */ +inline class Real(val value: Double) : FieldElement { + override val context: RealField + get() = RealField + + override fun unwrap(): Double = value + + override fun Double.wrap(): Real = Real(value) + + companion object +} + +/** + * A field for [Double] without boxing. Does not produce appropriate field element. + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") +object RealField : ExtendedField, Norm { + override val zero: Double + get() = 0.0 + + override val one: Double + get() = 1.0 + + override fun binaryOperation(operation: String, left: Double, right: Double): Double = when (operation) { + PowerOperations.POW_OPERATION -> left pow right + else -> super.binaryOperation(operation, left, right) + } + + override inline fun add(a: Double, b: Double): Double = a + b + override inline fun multiply(a: Double, k: Number): Double = a * k.toDouble() + + override inline fun multiply(a: Double, b: Double): Double = a * b + + override inline fun divide(a: Double, b: Double): Double = a / b + + override inline fun sin(arg: Double): Double = kotlin.math.sin(arg) + override inline fun cos(arg: Double): Double = kotlin.math.cos(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 sinh(arg: Double): Double = kotlin.math.sinh(arg) + override inline fun cosh(arg: Double): Double = kotlin.math.cosh(arg) + override inline fun tanh(arg: Double): Double = kotlin.math.tanh(arg) + override inline fun asinh(arg: Double): Double = kotlin.math.asinh(arg) + override inline fun acosh(arg: Double): Double = kotlin.math.acosh(arg) + override inline fun atanh(arg: Double): Double = kotlin.math.atanh(arg) + + override inline fun power(arg: Double, pow: Number): Double = arg.kpow(pow.toDouble()) + override inline fun exp(arg: Double): Double = kotlin.math.exp(arg) + override inline fun ln(arg: Double): Double = kotlin.math.ln(arg) + + override inline fun norm(arg: Double): Double = abs(arg) + + override inline fun Double.unaryMinus(): Double = -this + override inline fun Double.plus(b: Double): Double = this + b + override inline fun Double.minus(b: Double): Double = this - b + override inline fun Double.times(b: Double): Double = this * b + override inline fun Double.div(b: Double): Double = this / b +} + +/** + * A field for [Float] without boxing. Does not produce appropriate field element. + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") +object FloatField : ExtendedField, Norm { + override val zero: Float + get() = 0.0f + + override val one: Float + get() = 1.0f + + override fun binaryOperation(operation: String, left: Float, right: Float): Float = when (operation) { + PowerOperations.POW_OPERATION -> left pow right + else -> super.binaryOperation(operation, left, right) + } + + override inline fun add(a: Float, b: Float): Float = a + b + override inline fun multiply(a: Float, k: Number): Float = a * k.toFloat() + + override inline fun multiply(a: Float, b: Float): Float = a * b + + override inline fun divide(a: Float, b: Float): Float = a / b + + override inline fun sin(arg: Float): Float = kotlin.math.sin(arg) + override inline fun cos(arg: Float): Float = kotlin.math.cos(arg) + override inline fun tan(arg: Float): Float = kotlin.math.tan(arg) + override inline fun acos(arg: Float): Float = kotlin.math.acos(arg) + override inline fun asin(arg: Float): Float = kotlin.math.asin(arg) + override inline fun atan(arg: Float): Float = kotlin.math.atan(arg) + + override inline fun sinh(arg: Float): Float = kotlin.math.sinh(arg) + override inline fun cosh(arg: Float): Float = kotlin.math.cosh(arg) + override inline fun tanh(arg: Float): Float = kotlin.math.tanh(arg) + override inline fun asinh(arg: Float): Float = kotlin.math.asinh(arg) + override inline fun acosh(arg: Float): Float = kotlin.math.acosh(arg) + override inline fun atanh(arg: Float): Float = kotlin.math.atanh(arg) + + override inline fun power(arg: Float, pow: Number): Float = arg.kpow(pow.toFloat()) + override inline fun exp(arg: Float): Float = kotlin.math.exp(arg) + override inline fun ln(arg: Float): Float = kotlin.math.ln(arg) + + override inline fun norm(arg: Float): Float = abs(arg) + + override inline fun Float.unaryMinus(): Float = -this + override inline fun Float.plus(b: Float): Float = this + b + override inline fun Float.minus(b: Float): Float = this - b + override inline fun Float.times(b: Float): Float = this * b + override inline fun Float.div(b: Float): Float = this / b +} + +/** + * A field for [Int] without boxing. Does not produce corresponding ring element. + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") +object IntRing : Ring, Norm, RemainderDivisionOperations { + override val zero: Int + get() = 0 + + override val one: Int + get() = 1 + + override inline fun add(a: Int, b: Int): Int = a + b + override inline fun multiply(a: Int, k: Number): Int = k.toInt() * a + + override inline fun multiply(a: Int, b: Int): Int = a * b + + override inline fun norm(arg: Int): Int = abs(arg) + + override inline fun Int.unaryMinus(): Int = -this + override inline fun Int.plus(b: Int): Int = this + b + override inline fun Int.minus(b: Int): Int = this - b + override inline fun Int.times(b: Int): Int = this * b + + override fun Int.rem(arg: Int): Int = this % arg + override fun Int.div(arg: Int): Int = this / arg +} + +/** + * A field for [Short] without boxing. Does not produce appropriate ring element. + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") +object ShortRing : Ring, Norm, RemainderDivisionOperations { + override val zero: Short + get() = 0 + + override val one: Short + get() = 1 + + override inline fun add(a: Short, b: Short): Short = (a + b).toShort() + override inline fun multiply(a: Short, k: Number): Short = (a * k.toShort()).toShort() + + override inline fun multiply(a: Short, b: Short): Short = (a * b).toShort() + + override fun norm(arg: Short): Short = if (arg > 0) arg else (-arg).toShort() + + override inline fun Short.unaryMinus(): Short = (-this).toShort() + override inline fun Short.plus(b: Short): Short = (this + b).toShort() + override inline fun Short.minus(b: Short): Short = (this - b).toShort() + override inline fun Short.times(b: Short): Short = (this * b).toShort() + + override fun Short.rem(arg: Short): Short = (this % arg).toShort() + override fun Short.div(arg: Short): Short = (this / arg).toShort() +} + +/** + * A field for [Byte] without boxing. Does not produce appropriate ring element. + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") +object ByteRing : Ring, Norm, RemainderDivisionOperations { + override val zero: Byte + get() = 0 + + override val one: Byte + get() = 1 + + override inline fun add(a: Byte, b: Byte): Byte = (a + b).toByte() + override inline fun multiply(a: Byte, k: Number): Byte = (a * k.toByte()).toByte() + + override inline fun multiply(a: Byte, b: Byte): Byte = (a * b).toByte() + + override fun norm(arg: Byte): Byte = if (arg > 0) arg else (-arg).toByte() + + override inline fun Byte.unaryMinus(): Byte = (-this).toByte() + override inline fun Byte.plus(b: Byte): Byte = (this + b).toByte() + override inline fun Byte.minus(b: Byte): Byte = (this - b).toByte() + override inline fun Byte.times(b: Byte): Byte = (this * b).toByte() + + override fun Byte.rem(arg: Byte): Byte = (this % arg).toByte() + override fun Byte.div(arg: Byte): Byte = (this / arg).toByte() +} + +/** + * A field for [Double] without boxing. Does not produce appropriate ring element. + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") +object LongRing : Ring, Norm, RemainderDivisionOperations { + override val zero: Long + get() = 0 + + override val one: Long + get() = 1 + + override inline fun add(a: Long, b: Long): Long = (a + b) + override inline fun multiply(a: Long, k: Number): Long = a * k.toLong() + + override inline fun multiply(a: Long, b: Long): Long = a * b + + override fun norm(arg: Long): Long = abs(arg) + + override inline fun Long.unaryMinus(): Long = (-this) + override inline fun Long.plus(b: Long): Long = (this + b) + override inline fun Long.minus(b: Long): Long = (this - b) + override inline fun Long.times(b: Long): Long = (this * b) + + override fun Long.rem(arg: Long): Long = this % arg + override fun Long.div(arg: Long): Long = this / arg +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt new file mode 100644 index 000000000..735fbb342 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -0,0 +1,335 @@ +package scientifik.kmath.operations + +/** + * A container for trigonometric operations for specific type. + * + * @param T the type of element of this structure. + */ +interface TrigonometricOperations : Algebra { + /** + * Computes the sine of [arg]. + */ + fun sin(arg: T): T + + /** + * Computes the cosine of [arg]. + */ + fun cos(arg: T): T + + /** + * Computes the tangent of [arg]. + */ + fun tan(arg: T): T + + /** + * Computes the inverse sine of [arg]. + */ + fun asin(arg: T): T + + /** + * Computes the inverse cosine of [arg]. + */ + fun acos(arg: T): T + + /** + * Computes the inverse tangent of [arg]. + */ + fun atan(arg: T): T + + companion object { + /** + * The identifier of sine. + */ + const val SIN_OPERATION: String = "sin" + + /** + * The identifier of cosine. + */ + const val COS_OPERATION: String = "cos" + + /** + * The identifier of tangent. + */ + const val TAN_OPERATION: String = "tan" + + /** + * The identifier of inverse sine. + */ + const val ASIN_OPERATION: String = "asin" + + /** + * The identifier of inverse cosine. + */ + const val ACOS_OPERATION: String = "acos" + + /** + * The identifier of inverse tangent. + */ + const val ATAN_OPERATION: String = "atan" + } +} + +/** + * Computes the sine of [arg]. + */ +fun >> sin(arg: T): T = arg.context.sin(arg) + +/** + * Computes the cosine of [arg]. + */ +fun >> cos(arg: T): T = arg.context.cos(arg) + +/** + * Computes the tangent of [arg]. + */ +fun >> tan(arg: T): T = arg.context.tan(arg) + +/** + * Computes the inverse sine of [arg]. + */ +fun >> asin(arg: T): T = arg.context.asin(arg) + +/** + * Computes the inverse cosine of [arg]. + */ +fun >> acos(arg: T): T = arg.context.acos(arg) + +/** + * Computes the inverse tangent of [arg]. + */ +fun >> atan(arg: T): T = arg.context.atan(arg) + +/** + * A container for hyperbolic trigonometric operations for specific type. + * + * @param T the type of element of this structure. + */ +interface HyperbolicOperations : Algebra { + /** + * Computes the hyperbolic sine of [arg]. + */ + fun sinh(arg: T): T + + /** + * Computes the hyperbolic cosine of [arg]. + */ + fun cosh(arg: T): T + + /** + * Computes the hyperbolic tangent of [arg]. + */ + fun tanh(arg: T): T + + /** + * Computes the inverse hyperbolic sine of [arg]. + */ + fun asinh(arg: T): T + + /** + * Computes the inverse hyperbolic cosine of [arg]. + */ + fun acosh(arg: T): T + + /** + * Computes the inverse hyperbolic tangent of [arg]. + */ + fun atanh(arg: T): T + + companion object { + /** + * The identifier of hyperbolic sine. + */ + const val SINH_OPERATION: String = "sinh" + + /** + * The identifier of hyperbolic cosine. + */ + const val COSH_OPERATION: String = "cosh" + + /** + * The identifier of hyperbolic tangent. + */ + const val TANH_OPERATION: String = "tanh" + + /** + * The identifier of inverse hyperbolic sine. + */ + const val ASINH_OPERATION: String = "asinh" + + /** + * The identifier of inverse hyperbolic cosine. + */ + const val ACOSH_OPERATION: String = "acosh" + + /** + * The identifier of inverse hyperbolic tangent. + */ + const val ATANH_OPERATION: String = "atanh" + } +} + +/** + * Computes the hyperbolic sine of [arg]. + */ +fun >> sinh(arg: T): T = arg.context.sinh(arg) + +/** + * Computes the hyperbolic cosine of [arg]. + */ +fun >> cosh(arg: T): T = arg.context.cosh(arg) + +/** + * Computes the hyperbolic tangent of [arg]. + */ +fun >> tanh(arg: T): T = arg.context.tanh(arg) + +/** + * Computes the inverse hyperbolic sine of [arg]. + */ +fun >> asinh(arg: T): T = arg.context.asinh(arg) + +/** + * Computes the inverse hyperbolic cosine of [arg]. + */ +fun >> acosh(arg: T): T = arg.context.acosh(arg) + +/** + * Computes the inverse hyperbolic tangent of [arg]. + */ +fun >> atanh(arg: T): T = arg.context.atanh(arg) + +/** + * A context extension to include power operations based on exponentiation. + * + * @param T the type of element of this structure. + */ +interface PowerOperations : Algebra { + /** + * Raises [arg] to the power [pow]. + */ + fun power(arg: T, pow: Number): T + + /** + * Computes the square root of the value [arg]. + */ + fun sqrt(arg: T): T = power(arg, 0.5) + + /** + * Raises this value to the power [pow]. + */ + infix fun T.pow(pow: Number): T = power(this, pow) + + companion object { + /** + * The identifier of exponentiation. + */ + const val POW_OPERATION: String = "pow" + + /** + * The identifier of square root. + */ + const val SQRT_OPERATION: String = "sqrt" + } +} + +/** + * Raises this element to the power [pow]. + * + * @receiver the base. + * @param power the exponent. + * @return the base raised to the power. + */ +infix fun >> T.pow(power: Double): T = context.power(this, power) + +/** + * Computes the square root of the value [arg]. + */ +fun >> sqrt(arg: T): T = arg pow 0.5 + +/** + * Computes the square of the value [arg]. + */ +fun >> sqr(arg: T): T = arg pow 2.0 + +/** + * A container for operations related to `exp` and `ln` functions. + * + * @param T the type of element of this structure. + */ +interface ExponentialOperations : Algebra { + /** + * Computes Euler's number `e` raised to the power of the value [arg]. + */ + fun exp(arg: T): T + + /** + * Computes the natural logarithm (base `e`) of the value [arg]. + */ + fun ln(arg: T): T + + companion object { + /** + * The identifier of exponential function. + */ + const val EXP_OPERATION: String = "exp" + + /** + * The identifier of natural logarithm. + */ + const val LN_OPERATION: String = "ln" + } +} + +/** + * The identifier of exponential function. + */ +fun >> exp(arg: T): T = arg.context.exp(arg) + +/** + * The identifier of natural logarithm. + */ +fun >> ln(arg: T): T = arg.context.ln(arg) + +/** + * A container for norm functional on element. + * + * @param T the type of element having norm defined. + * @param R the type of norm. + */ +interface Norm { + /** + * Computes the norm of [arg] (i.e. absolute value or vector length). + */ + fun norm(arg: T): R +} + +/** + * Computes the norm of [arg] (i.e. absolute value or vector length). + */ +fun >, R> norm(arg: T): R = arg.context.norm(arg) + +interface RemainderDivisionOperations : RingOperations { + /** + * Calculates the remainder of dividing this value by [arg]. + */ + operator fun T.rem(arg: T): T + + /** + * Performs the floored division of this value by [arg]. + */ + operator fun T.div(arg: T): T + + override fun binaryOperation(operation: String, left: T, right: T): T = when (operation) { + REM_OPERATION -> left % right + DIV_OPERATION -> left / right + else -> super.binaryOperation(operation, left, right) + } + + companion object { + const val REM_OPERATION = "rem" + const val DIV_OPERATION = "div" + } +} + +infix fun >> T.rem(arg: T): T = arg.context { this@rem rem arg } +infix fun >> T.div(arg: T): T = arg.context { this@div div arg } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt similarity index 56% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt index dc65b12c4..4cbb565c1 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt @@ -1,33 +1,33 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.Field -import kscience.kmath.operations.FieldElement +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.FieldElement -public class BoxingNDField>( - public override val shape: IntArray, - public override val elementContext: F, - public val bufferFactory: BufferFactory +class BoxingNDField>( + override val shape: IntArray, + override val elementContext: F, + val bufferFactory: BufferFactory ) : BufferedNDField { - public override val zero: BufferedNDFieldElement by lazy { produce { zero } } - public override val one: BufferedNDFieldElement by lazy { produce { one } } - public override val strides: Strides = DefaultStrides(shape) - public fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = + override val strides: Strides = DefaultStrides(shape) + + fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) - public override fun check(vararg elements: NDBuffer): Array> { - require(elements.all { it.strides == strides }) { "Element strides are not the same as context strides" } - return elements + override fun check(vararg elements: NDBuffer) { + if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides") } - public override fun produce(initializer: F.(IntArray) -> T): BufferedNDFieldElement = + override val zero: BufferedNDFieldElement by lazy { produce { zero } } + override val one: BufferedNDFieldElement by lazy { produce { one } } + + override fun produce(initializer: F.(IntArray) -> T): BufferedNDFieldElement = BufferedNDFieldElement( this, buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }) - public override fun map(arg: NDBuffer, transform: F.(T) -> T): BufferedNDFieldElement { + override fun map(arg: NDBuffer, transform: F.(T) -> T): BufferedNDFieldElement { check(arg) - return BufferedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) }) @@ -37,7 +37,7 @@ public class BoxingNDField>( } - public override fun mapIndexed( + override fun mapIndexed( arg: NDBuffer, transform: F.(index: IntArray, T) -> T ): BufferedNDFieldElement { @@ -56,7 +56,7 @@ public class BoxingNDField>( // return BufferedNDFieldElement(this, buffer) } - public override fun combine( + override fun combine( a: NDBuffer, b: NDBuffer, transform: F.(T, T) -> T @@ -67,15 +67,15 @@ public class BoxingNDField>( buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) } - public override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = BufferedNDFieldElement(this@BoxingNDField, buffer) } -public inline fun , R> F.nd( +inline fun , R> F.nd( noinline bufferFactory: BufferFactory, vararg shape: Int, action: NDField.() -> R ): R { - val ndfield = NDField.boxing(this, *shape, bufferFactory = bufferFactory) + val ndfield: BoxingNDField = NDField.boxing(this, *shape, bufferFactory = bufferFactory) return ndfield.action() } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDRing.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDRing.kt similarity index 84% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDRing.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDRing.kt index b6794984c..f7be95736 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BoxingNDRing.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDRing.kt @@ -1,24 +1,26 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.Ring -import kscience.kmath.operations.RingElement +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.RingElement -public class BoxingNDRing>( +class BoxingNDRing>( override val shape: IntArray, override val elementContext: R, - public val bufferFactory: BufferFactory + val bufferFactory: BufferFactory ) : BufferedNDRing { + override val strides: Strides = DefaultStrides(shape) + + fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = + bufferFactory(size, initializer) + + override fun check(vararg elements: NDBuffer) { + if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides") + } + override val zero: BufferedNDRingElement by lazy { produce { zero } } override val one: BufferedNDRingElement by lazy { produce { one } } - public fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) - - override fun check(vararg elements: NDBuffer): Array> { - if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides") - return elements - } - override fun produce(initializer: R.(IntArray) -> T): BufferedNDRingElement = BufferedNDRingElement( this, @@ -60,7 +62,6 @@ public class BoxingNDRing>( transform: R.(T, T) -> T ): BufferedNDRingElement { check(a, b) - return BufferedNDRingElement( this, buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt new file mode 100644 index 000000000..00832b69c --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt @@ -0,0 +1,45 @@ +package scientifik.kmath.structures + +import kotlin.reflect.KClass + +/** + * A context that allows to operate on a [MutableBuffer] as on 2d array + */ +class BufferAccessor2D(val type: KClass, val rowNum: Int, val colNum: Int) { + + operator fun Buffer.get(i: Int, j: Int): T = get(i + colNum * j) + + operator fun MutableBuffer.set(i: Int, j: Int, value: T) { + set(i + colNum * j, value) + } + + inline fun create(init: (i: Int, j: Int) -> T): MutableBuffer = + MutableBuffer.auto(type, rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) } + + fun create(mat: Structure2D): MutableBuffer = create { i, j -> mat[i, j] } + + //TODO optimize wrapper + fun MutableBuffer.collect(): Structure2D = + NDStructure.auto(type, rowNum, colNum) { (i, j) -> get(i, j) }.as2D() + + + inner class Row(val buffer: MutableBuffer, val rowIndex: Int) : MutableBuffer { + override val size: Int get() = colNum + + override fun get(index: Int): T = buffer[rowIndex, index] + + override fun set(index: Int, value: T) { + buffer[rowIndex, index] = value + } + + override fun copy(): MutableBuffer = MutableBuffer.auto(type, colNum) { get(it) } + + override fun iterator(): Iterator = (0 until colNum).map(::get).iterator() + + } + + /** + * Get row + */ + fun MutableBuffer.row(i: Int): Row = Row(this, i) +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt new file mode 100644 index 000000000..06922c56f --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt @@ -0,0 +1,44 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.* + +interface BufferedNDAlgebra : NDAlgebra> { + val strides: Strides + + override fun check(vararg elements: NDBuffer) { + if (!elements.all { it.strides == this.strides }) error("Strides mismatch") + } + + /** + * Convert any [NDStructure] to buffered structure using strides from this context. + * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over + * indices. + * + * If the argument is [NDBuffer] with different strides structure, the new element will be produced. + */ + fun NDStructure.toBuffer(): NDBuffer { + return if (this is NDBuffer && this.strides == this@BufferedNDAlgebra.strides) { + this + } else { + produce { index -> get(index) } + } + } + + /** + * Convert a buffer to element of this algebra + */ + fun NDBuffer.toElement(): MathElement> +} + + +interface BufferedNDSpace> : NDSpace>, BufferedNDAlgebra { + override fun NDBuffer.toElement(): SpaceElement, *, out BufferedNDSpace> +} + +interface BufferedNDRing> : NDRing>, BufferedNDSpace { + override fun NDBuffer.toElement(): RingElement, *, out BufferedNDRing> +} + +interface BufferedNDField> : NDField>, BufferedNDRing { + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> +} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt similarity index 70% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDElement.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt index d53702566..d1d622b23 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/BufferedNDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt @@ -1,11 +1,11 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.* +import scientifik.kmath.operations.* /** * Base class for an element with context, containing strides */ -public abstract class BufferedNDElement : NDBuffer(), NDElement> { +abstract class BufferedNDElement : NDBuffer(), NDElement> { abstract override val context: BufferedNDAlgebra override val strides: Strides get() = context.strides @@ -13,7 +13,7 @@ public abstract class BufferedNDElement : NDBuffer(), NDElement>( +class BufferedNDSpaceElement>( override val context: BufferedNDSpace, override val buffer: Buffer ) : BufferedNDElement(), SpaceElement, BufferedNDSpaceElement, BufferedNDSpace> { @@ -26,10 +26,11 @@ public class BufferedNDSpaceElement>( } } -public class BufferedNDRingElement>( +class BufferedNDRingElement>( override val context: BufferedNDRing, override val buffer: Buffer ) : BufferedNDElement(), RingElement, BufferedNDRingElement, BufferedNDRing> { + override fun unwrap(): NDBuffer = this override fun NDBuffer.wrap(): BufferedNDRingElement { @@ -38,10 +39,11 @@ public class BufferedNDRingElement>( } } -public class BufferedNDFieldElement>( +class BufferedNDFieldElement>( override val context: BufferedNDField, override val buffer: Buffer ) : BufferedNDElement(), FieldElement, BufferedNDFieldElement, BufferedNDField> { + override fun unwrap(): NDBuffer = this override fun NDBuffer.wrap(): BufferedNDFieldElement { @@ -54,7 +56,7 @@ public class BufferedNDFieldElement>( /** * Element by element application of any operation on elements to the whole array. Just like in numpy. */ -public operator fun > Function1.invoke(ndElement: BufferedNDElement): MathElement> = +operator fun > Function1.invoke(ndElement: BufferedNDElement): MathElement> = ndElement.context.run { map(ndElement) { invoke(it) }.toElement() } /* plus and minus */ @@ -62,13 +64,13 @@ public operator fun > Function1.invoke(ndElement: Bu /** * Summation operation for [BufferedNDElement] and single element */ -public operator fun > BufferedNDElement.plus(arg: T): NDElement> = +operator fun > BufferedNDElement.plus(arg: T): NDElement> = context.map(this) { it + arg }.wrap() /** * Subtraction operation between [BufferedNDElement] and single element */ -public operator fun > BufferedNDElement.minus(arg: T): NDElement> = +operator fun > BufferedNDElement.minus(arg: T): NDElement> = context.map(this) { it - arg }.wrap() /* prod and div */ @@ -76,11 +78,11 @@ public operator fun > BufferedNDElement.minus(arg: T /** * Product operation for [BufferedNDElement] and single element */ -public operator fun > BufferedNDElement.times(arg: T): NDElement> = +operator fun > BufferedNDElement.times(arg: T): NDElement> = context.map(this) { it * arg }.wrap() /** * Division operation between [BufferedNDElement] and single element */ -public operator fun > BufferedNDElement.div(arg: T): NDElement> = +operator fun > BufferedNDElement.div(arg: T): NDElement> = context.map(this) { it / arg }.wrap() diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt new file mode 100644 index 000000000..5fdf79e88 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -0,0 +1,270 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.complex +import kotlin.reflect.KClass + +/** + * Function that produces [Buffer] from its size and function that supplies values. + * + * @param T the type of buffer. + */ +typealias BufferFactory = (Int, (Int) -> T) -> Buffer + +/** + * Function that produces [MutableBuffer] from its size and function that supplies values. + * + * @param T the type of buffer. + */ +typealias MutableBufferFactory = (Int, (Int) -> T) -> MutableBuffer + +/** + * A generic immutable random-access structure for both primitives and objects. + * + * @param T the type of elements contained in the buffer. + */ +interface Buffer { + /** + * The size of this buffer. + */ + val size: Int + + /** + * Gets element at given index. + */ + operator fun get(index: Int): T + + /** + * Iterates over all elements. + */ + operator fun iterator(): Iterator + + /** + * Checks content equality with another buffer. + */ + fun contentEquals(other: Buffer<*>): Boolean = + asSequence().mapIndexed { index, value -> value == other[index] }.all { it } + + companion object { + inline fun real(size: Int, initializer: (Int) -> Double): RealBuffer { + val array = DoubleArray(size) { initializer(it) } + return RealBuffer(array) + } + + /** + * Create a boxing buffer of given type + */ + inline fun boxing(size: Int, initializer: (Int) -> T): Buffer = ListBuffer(List(size, initializer)) + + @Suppress("UNCHECKED_CAST") + inline fun auto(type: KClass, size: Int, crossinline initializer: (Int) -> T): Buffer { + //TODO add resolution based on Annotation or companion resolution + return when (type) { + Double::class -> RealBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer + Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as Buffer + Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer + Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer + Complex::class -> complex(size) { initializer(it) as Complex } as Buffer + else -> boxing(size, initializer) + } + } + + /** + * Create most appropriate immutable buffer for given type avoiding boxing wherever possible + */ + @Suppress("UNCHECKED_CAST") + inline fun auto(size: Int, crossinline initializer: (Int) -> T): Buffer = + auto(T::class, size, initializer) + } +} + +/** + * Creates a sequence that returns all elements from this [Buffer]. + */ +fun Buffer.asSequence(): Sequence = Sequence(::iterator) + +/** + * Creates an iterable that returns all elements from this [Buffer]. + */ +fun Buffer.asIterable(): Iterable = Iterable(::iterator) + +/** + * Returns an [IntRange] of the valid indices for this [Buffer]. + */ +val Buffer<*>.indices: IntRange get() = 0 until size + +/** + * A generic mutable random-access structure for both primitives and objects. + * + * @param T the type of elements contained in the buffer. + */ +interface MutableBuffer : Buffer { + /** + * Sets the array element at the specified [index] to the specified [value]. + */ + operator fun set(index: Int, value: T) + + /** + * Returns a shallow copy of the buffer. + */ + fun copy(): MutableBuffer + + companion object { + /** + * Create a boxing mutable buffer of given type + */ + inline fun boxing(size: Int, initializer: (Int) -> T): MutableBuffer = + MutableListBuffer(MutableList(size, initializer)) + + @Suppress("UNCHECKED_CAST") + inline fun auto(type: KClass, size: Int, initializer: (Int) -> T): MutableBuffer { + return when (type) { + Double::class -> RealBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer + Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as MutableBuffer + Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer + Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer + else -> boxing(size, initializer) + } + } + + /** + * Create most appropriate mutable buffer for given type avoiding boxing wherever possible + */ + @Suppress("UNCHECKED_CAST") + inline fun auto(size: Int, initializer: (Int) -> T): MutableBuffer = + auto(T::class, size, initializer) + + val real: MutableBufferFactory = { size: Int, initializer: (Int) -> Double -> + RealBuffer(DoubleArray(size) { initializer(it) }) + } + } +} + +/** + * [Buffer] implementation over [List]. + * + * @param T the type of elements contained in the buffer. + * @property list The underlying list. + */ +inline class ListBuffer(val list: List) : Buffer { + override val size: Int + get() = list.size + + override fun get(index: Int): T = list[index] + + override fun iterator(): Iterator = list.iterator() +} + +/** + * Returns an [ListBuffer] that wraps the original list. + */ +fun List.asBuffer(): ListBuffer = ListBuffer(this) + +/** + * Creates a new [ListBuffer] with the specified [size], where each element is calculated by calling the specified + * [init] function. + * + * The function [init] is called for each array element sequentially starting from the first one. + * It should return the value for an array element given its index. + */ +inline fun ListBuffer(size: Int, init: (Int) -> T): ListBuffer = List(size, init).asBuffer() + +/** + * [MutableBuffer] implementation over [MutableList]. + * + * @param T the type of elements contained in the buffer. + * @property list The underlying list. + */ +inline class MutableListBuffer(val list: MutableList) : MutableBuffer { + + override val size: Int + get() = list.size + + override fun get(index: Int): T = list[index] + + override fun set(index: Int, value: T) { + list[index] = value + } + + override fun iterator(): Iterator = list.iterator() + override fun copy(): MutableBuffer = MutableListBuffer(ArrayList(list)) +} + +/** + * [MutableBuffer] implementation over [Array]. + * + * @param T the type of elements contained in the buffer. + * @property array The underlying array. + */ +class ArrayBuffer(private val array: Array) : MutableBuffer { + // Can't inline because array is invariant + override val size: Int + get() = array.size + + override fun get(index: Int): T = array[index] + + override fun set(index: Int, value: T) { + array[index] = value + } + + override fun iterator(): Iterator = array.iterator() + + override fun copy(): MutableBuffer = ArrayBuffer(array.copyOf()) +} + +/** + * Returns an [ArrayBuffer] that wraps the original array. + */ +fun Array.asBuffer(): ArrayBuffer = ArrayBuffer(this) + +/** + * Immutable wrapper for [MutableBuffer]. + * + * @param T the type of elements contained in the buffer. + * @property buffer The underlying buffer. + */ +inline class ReadOnlyBuffer(val buffer: MutableBuffer) : Buffer { + override val size: Int get() = buffer.size + + override fun get(index: Int): T = buffer[index] + + override fun iterator(): Iterator = buffer.iterator() +} + +/** + * A buffer with content calculated on-demand. The calculated content is not stored, so it is recalculated on each call. + * Useful when one needs single element from the buffer. + * + * @param T the type of elements provided by the buffer. + */ +class VirtualBuffer(override val size: Int, private val generator: (Int) -> T) : Buffer { + override fun get(index: Int): T { + if (index < 0 || index >= size) throw IndexOutOfBoundsException("Expected index from 0 to ${size - 1}, but found $index") + return generator(index) + } + + override fun iterator(): Iterator = (0 until size).asSequence().map(generator).iterator() + + override fun contentEquals(other: Buffer<*>): Boolean { + return if (other is VirtualBuffer) { + this.size == other.size && this.generator == other.generator + } else { + super.contentEquals(other) + } + } +} + +/** + * Convert this buffer to read-only buffer. + */ +fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) ReadOnlyBuffer(this) else this + +/** + * Typealias for buffer transformations. + */ +typealias BufferTransform = (Buffer) -> Buffer + +/** + * Typealias for buffer transformations with suspend function. + */ +typealias SuspendBufferTransform = suspend (Buffer) -> Buffer diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ComplexNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt similarity index 70% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ComplexNDField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt index f1f1074e5..370d8ff4d 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ComplexNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt @@ -1,18 +1,16 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.Complex -import kscience.kmath.operations.ComplexField -import kscience.kmath.operations.FieldElement -import kscience.kmath.operations.complex -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.ComplexField +import scientifik.kmath.operations.FieldElement +import scientifik.kmath.operations.complex -public typealias ComplexNDElement = BufferedNDFieldElement +typealias ComplexNDElement = BufferedNDFieldElement /** * An optimized nd-field for complex numbers */ -public class ComplexNDField(override val shape: IntArray) : +class ComplexNDField(override val shape: IntArray) : BufferedNDField, ExtendedNDField> { @@ -21,7 +19,7 @@ public class ComplexNDField(override val shape: IntArray) : override val zero: ComplexNDElement by lazy { produce { zero } } override val one: ComplexNDElement by lazy { produce { one } } - public inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Complex): Buffer = + inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Complex): Buffer = Buffer.complex(size) { initializer(it) } /** @@ -97,7 +95,7 @@ public class ComplexNDField(override val shape: IntArray) : /** * Fast element production using function inlining */ -public inline fun BufferedNDField.produceInline(initializer: ComplexField.(Int) -> Complex): ComplexNDElement { +inline fun BufferedNDField.produceInline(crossinline initializer: ComplexField.(Int) -> Complex): ComplexNDElement { val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.initializer(offset) } return BufferedNDFieldElement(this, buffer) } @@ -105,13 +103,13 @@ public inline fun BufferedNDField.produceInline(initializ /** * Map one [ComplexNDElement] using function with indices. */ -public inline fun ComplexNDElement.mapIndexed(transform: ComplexField.(index: IntArray, Complex) -> Complex): ComplexNDElement = +inline fun ComplexNDElement.mapIndexed(crossinline transform: ComplexField.(index: IntArray, Complex) -> Complex): ComplexNDElement = context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) } /** * Map one [ComplexNDElement] using function without indices. */ -public inline fun ComplexNDElement.map(transform: ComplexField.(Complex) -> Complex): ComplexNDElement { +inline fun ComplexNDElement.map(crossinline transform: ComplexField.(Complex) -> Complex): ComplexNDElement { val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.transform(buffer[offset]) } return BufferedNDFieldElement(context, buffer) } @@ -119,35 +117,37 @@ public inline fun ComplexNDElement.map(transform: ComplexField.(Complex) -> Comp /** * Element by element application of any operation on elements to the whole array. Just like in numpy */ -public operator fun Function1.invoke(ndElement: ComplexNDElement): ComplexNDElement = +operator fun Function1.invoke(ndElement: ComplexNDElement): ComplexNDElement = ndElement.map { this@invoke(it) } + /* plus and minus */ /** * Summation operation for [BufferedNDElement] and single element */ -public operator fun ComplexNDElement.plus(arg: Complex): ComplexNDElement = map { it + arg } +operator fun ComplexNDElement.plus(arg: Complex): ComplexNDElement = map { it + arg } /** * Subtraction operation between [BufferedNDElement] and single element */ -public operator fun ComplexNDElement.minus(arg: Complex): ComplexNDElement = map { it - arg } +operator fun ComplexNDElement.minus(arg: Complex): ComplexNDElement = + map { it - arg } -public operator fun ComplexNDElement.plus(arg: Double): ComplexNDElement = map { it + arg } -public operator fun ComplexNDElement.minus(arg: Double): ComplexNDElement = map { it - arg } +operator fun ComplexNDElement.plus(arg: Double): ComplexNDElement = + map { it + arg } -public fun NDField.Companion.complex(vararg shape: Int): ComplexNDField = ComplexNDField(shape) +operator fun ComplexNDElement.minus(arg: Double): ComplexNDElement = + map { it - arg } -public fun NDElement.Companion.complex( - vararg shape: Int, - initializer: ComplexField.(IntArray) -> Complex -): ComplexNDElement = NDField.complex(*shape).produce(initializer) +fun NDField.Companion.complex(vararg shape: Int): ComplexNDField = ComplexNDField(shape) + +fun NDElement.Companion.complex(vararg shape: Int, initializer: ComplexField.(IntArray) -> Complex): ComplexNDElement = + NDField.complex(*shape).produce(initializer) /** * Produce a context for n-dimensional operations inside this real field */ -public inline fun ComplexField.nd(vararg shape: Int, action: ComplexNDField.() -> R): R { - contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } - return NDField.complex(*shape).action() +inline fun ComplexField.nd(vararg shape: Int, action: ComplexNDField.() -> R): R { + return NDField.complex(*shape).run(action) } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt similarity index 86% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ExtendedNDField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index a9fa2763b..24aa48c6b 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -1,6 +1,6 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.ExtendedField +import scientifik.kmath.operations.ExtendedField /** * [ExtendedField] over [NDStructure]. @@ -9,7 +9,7 @@ import kscience.kmath.operations.ExtendedField * @param N the type of ND structure. * @param F the extended field of structure elements. */ -public interface ExtendedNDField, N : NDStructure> : NDField, ExtendedField +interface ExtendedNDField, N : NDStructure> : NDField, ExtendedField ///** // * NDField that supports [ExtendedField] operations on its elements diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/FlaggedBuffer.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/FlaggedBuffer.kt similarity index 51% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/FlaggedBuffer.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/FlaggedBuffer.kt index 4965e37cf..a2d0a71b3 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/FlaggedBuffer.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/FlaggedBuffer.kt @@ -1,4 +1,4 @@ -package kscience.kmath.structures +package scientifik.kmath.structures import kotlin.experimental.and @@ -7,7 +7,7 @@ import kotlin.experimental.and * * @property mask bit mask value of this flag. */ -public enum class ValueFlag(public val mask: Byte) { +enum class ValueFlag(val mask: Byte) { /** * Reports the value is NaN. */ @@ -32,24 +32,23 @@ public enum class ValueFlag(public val mask: Byte) { /** * A buffer with flagged values. */ -public interface FlaggedBuffer : Buffer { - public fun getFlag(index: Int): Byte +interface FlaggedBuffer : Buffer { + fun getFlag(index: Int): Byte } /** * The value is valid if all flags are down */ -public fun FlaggedBuffer<*>.isValid(index: Int): Boolean = getFlag(index) != 0.toByte() +fun FlaggedBuffer<*>.isValid(index: Int): Boolean = getFlag(index) != 0.toByte() -public fun FlaggedBuffer<*>.hasFlag(index: Int, flag: ValueFlag): Boolean = (getFlag(index) and flag.mask) != 0.toByte() +fun FlaggedBuffer<*>.hasFlag(index: Int, flag: ValueFlag): Boolean = (getFlag(index) and flag.mask) != 0.toByte() -public fun FlaggedBuffer<*>.isMissing(index: Int): Boolean = hasFlag(index, ValueFlag.MISSING) +fun FlaggedBuffer<*>.isMissing(index: Int): Boolean = hasFlag(index, ValueFlag.MISSING) /** * A real buffer which supports flags for each value like NaN or Missing */ -public class FlaggedRealBuffer(public val values: DoubleArray, public val flags: ByteArray) : FlaggedBuffer, - Buffer { +class FlaggedRealBuffer(val values: DoubleArray, val flags: ByteArray) : FlaggedBuffer, Buffer { init { require(values.size == flags.size) { "Values and flags must have the same dimensions" } } @@ -58,16 +57,17 @@ public class FlaggedRealBuffer(public val values: DoubleArray, public val flags: override val size: Int get() = values.size - override operator fun get(index: Int): Double? = if (isValid(index)) values[index] else null + override fun get(index: Int): Double? = if (isValid(index)) values[index] else null - override operator fun iterator(): Iterator = values.indices.asSequence().map { + override fun iterator(): Iterator = values.indices.asSequence().map { if (isValid(it)) values[it] else null }.iterator() } -public inline fun FlaggedRealBuffer.forEachValid(block: (Double) -> Unit) { - indices - .asSequence() - .filter(::isValid) - .forEach { block(values[it]) } +inline fun FlaggedRealBuffer.forEachValid(block: (Double) -> Unit) { + for (i in indices) { + if (isValid(i)) { + block(values[i]) + } + } } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/FloatBuffer.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/FloatBuffer.kt similarity index 58% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/FloatBuffer.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/FloatBuffer.kt index e96c45572..e42df8c14 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/FloatBuffer.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/FloatBuffer.kt @@ -1,21 +1,20 @@ -package kscience.kmath.structures +package scientifik.kmath.structures /** * Specialized [MutableBuffer] implementation over [FloatArray]. * * @property array the underlying array. - * @author Iaroslav Postovalov */ -public inline class FloatBuffer(public val array: FloatArray) : MutableBuffer { +inline class FloatBuffer(val array: FloatArray) : MutableBuffer { override val size: Int get() = array.size - override operator fun get(index: Int): Float = array[index] + override fun get(index: Int): Float = array[index] - override operator fun set(index: Int, value: Float) { + override fun set(index: Int, value: Float) { array[index] = value } - override operator fun iterator(): FloatIterator = array.iterator() + override fun iterator(): FloatIterator = array.iterator() override fun copy(): MutableBuffer = FloatBuffer(array.copyOf()) @@ -28,17 +27,17 @@ public inline class FloatBuffer(public val array: FloatArray) : MutableBuffer Float): FloatBuffer = FloatBuffer(FloatArray(size) { init(it) }) +inline fun FloatBuffer(size: Int, init: (Int) -> Float): FloatBuffer = FloatBuffer(FloatArray(size) { init(it) }) /** * Returns a new [FloatBuffer] of given elements. */ -public fun FloatBuffer(vararg floats: Float): FloatBuffer = FloatBuffer(floats) +fun FloatBuffer(vararg floats: Float): FloatBuffer = FloatBuffer(floats) /** * Returns a [FloatArray] containing all of the elements of this [MutableBuffer]. */ -public val MutableBuffer.array: FloatArray +val MutableBuffer.array: FloatArray get() = (if (this is FloatBuffer) array else FloatArray(size) { get(it) }) /** @@ -47,4 +46,4 @@ public val MutableBuffer.array: FloatArray * @receiver the array. * @return the new buffer. */ -public fun FloatArray.asBuffer(): FloatBuffer = FloatBuffer(this) +fun FloatArray.asBuffer(): FloatBuffer = FloatBuffer(this) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/IntBuffer.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/IntBuffer.kt similarity index 60% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/IntBuffer.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/IntBuffer.kt index 0fe68803b..a3f0f3c3e 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/IntBuffer.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/IntBuffer.kt @@ -1,23 +1,24 @@ -package kscience.kmath.structures +package scientifik.kmath.structures /** * Specialized [MutableBuffer] implementation over [IntArray]. * * @property array the underlying array. */ -public inline class IntBuffer(public val array: IntArray) : MutableBuffer { +inline class IntBuffer(val array: IntArray) : MutableBuffer { override val size: Int get() = array.size - override operator fun get(index: Int): Int = array[index] + override fun get(index: Int): Int = array[index] - override operator fun set(index: Int, value: Int) { + override fun set(index: Int, value: Int) { array[index] = value } - override operator fun iterator(): IntIterator = array.iterator() + override fun iterator(): IntIterator = array.iterator() override fun copy(): MutableBuffer = IntBuffer(array.copyOf()) + } /** @@ -27,17 +28,17 @@ public inline class IntBuffer(public val array: IntArray) : MutableBuffer { * The function [init] is called for each array element sequentially starting from the first one. * It should return the value for an buffer element given its index. */ -public inline fun IntBuffer(size: Int, init: (Int) -> Int): IntBuffer = IntBuffer(IntArray(size) { init(it) }) +inline fun IntBuffer(size: Int, init: (Int) -> Int): IntBuffer = IntBuffer(IntArray(size) { init(it) }) /** * Returns a new [IntBuffer] of given elements. */ -public fun IntBuffer(vararg ints: Int): IntBuffer = IntBuffer(ints) +fun IntBuffer(vararg ints: Int): IntBuffer = IntBuffer(ints) /** * Returns a [IntArray] containing all of the elements of this [MutableBuffer]. */ -public val MutableBuffer.array: IntArray +val MutableBuffer.array: IntArray get() = (if (this is IntBuffer) array else IntArray(size) { get(it) }) /** @@ -46,4 +47,4 @@ public val MutableBuffer.array: IntArray * @receiver the array. * @return the new buffer. */ -public fun IntArray.asBuffer(): IntBuffer = IntBuffer(this) +fun IntArray.asBuffer(): IntBuffer = IntBuffer(this) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/LongBuffer.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LongBuffer.kt similarity index 60% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/LongBuffer.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LongBuffer.kt index 87853c251..912656c68 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/LongBuffer.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LongBuffer.kt @@ -1,23 +1,24 @@ -package kscience.kmath.structures +package scientifik.kmath.structures /** * Specialized [MutableBuffer] implementation over [LongArray]. * * @property array the underlying array. */ -public inline class LongBuffer(public val array: LongArray) : MutableBuffer { +inline class LongBuffer(val array: LongArray) : MutableBuffer { override val size: Int get() = array.size - override operator fun get(index: Int): Long = array[index] + override fun get(index: Int): Long = array[index] - override operator fun set(index: Int, value: Long) { + override fun set(index: Int, value: Long) { array[index] = value } - override operator fun iterator(): LongIterator = array.iterator() + override fun iterator(): LongIterator = array.iterator() override fun copy(): MutableBuffer = LongBuffer(array.copyOf()) + } /** @@ -27,17 +28,17 @@ public inline class LongBuffer(public val array: LongArray) : MutableBuffer Long): LongBuffer = LongBuffer(LongArray(size) { init(it) }) +inline fun LongBuffer(size: Int, init: (Int) -> Long): LongBuffer = LongBuffer(LongArray(size) { init(it) }) /** * Returns a new [LongBuffer] of given elements. */ -public fun LongBuffer(vararg longs: Long): LongBuffer = LongBuffer(longs) +fun LongBuffer(vararg longs: Long): LongBuffer = LongBuffer(longs) /** * Returns a [IntArray] containing all of the elements of this [MutableBuffer]. */ -public val MutableBuffer.array: LongArray +val MutableBuffer.array: LongArray get() = (if (this is LongBuffer) array else LongArray(size) { get(it) }) /** @@ -46,4 +47,4 @@ public val MutableBuffer.array: LongArray * @receiver the array. * @return the new buffer. */ -public fun LongArray.asBuffer(): LongBuffer = LongBuffer(this) +fun LongArray.asBuffer(): LongBuffer = LongBuffer(this) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/MemoryBuffer.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/MemoryBuffer.kt new file mode 100644 index 000000000..1d0c87580 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/MemoryBuffer.kt @@ -0,0 +1,70 @@ +package scientifik.kmath.structures + +import scientifik.memory.* + +/** + * A non-boxing buffer over [Memory] object. + * + * @param T the type of elements contained in the buffer. + * @property memory the underlying memory segment. + * @property spec the spec of [T] type. + */ +open class MemoryBuffer(protected val memory: Memory, protected val spec: MemorySpec) : Buffer { + override val size: Int get() = memory.size / spec.objectSize + + private val reader: MemoryReader = memory.reader() + + override fun get(index: Int): T = reader.read(spec, spec.objectSize * index) + + override fun iterator(): Iterator = (0 until size).asSequence().map { get(it) }.iterator() + + + companion object { + fun create(spec: MemorySpec, size: Int): MemoryBuffer = + MemoryBuffer(Memory.allocate(size * spec.objectSize), spec) + + inline fun create( + spec: MemorySpec, + size: Int, + crossinline initializer: (Int) -> T + ): MemoryBuffer = + MutableMemoryBuffer(Memory.allocate(size * spec.objectSize), spec).also { buffer -> + (0 until size).forEach { + buffer[it] = initializer(it) + } + } + } +} + +/** + * A mutable non-boxing buffer over [Memory] object. + * + * @param T the type of elements contained in the buffer. + * @property memory the underlying memory segment. + * @property spec the spec of [T] type. + */ +class MutableMemoryBuffer(memory: Memory, spec: MemorySpec) : MemoryBuffer(memory, spec), + MutableBuffer { + + private val writer: MemoryWriter = memory.writer() + + override fun set(index: Int, value: T): Unit = writer.write(spec, spec.objectSize * index, value) + + override fun copy(): MutableBuffer = MutableMemoryBuffer(memory.copy(), spec) + + companion object { + fun create(spec: MemorySpec, size: Int): MutableMemoryBuffer = + MutableMemoryBuffer(Memory.allocate(size * spec.objectSize), spec) + + inline fun create( + spec: MemorySpec, + size: Int, + crossinline initializer: (Int) -> T + ): MutableMemoryBuffer = + MutableMemoryBuffer(Memory.allocate(size * spec.objectSize), spec).also { buffer -> + (0 until size).forEach { + buffer[it] = initializer(it) + } + } + } +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt new file mode 100644 index 000000000..f09db3c72 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -0,0 +1,155 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.Space + + +/** + * An exception is thrown when the expected ans actual shape of NDArray differs + */ +class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException() + + +/** + * The base interface for all nd-algebra implementations + * @param T the type of nd-structure element + * @param C the type of the element context + * @param N the type of the structure + */ +interface NDAlgebra> { + val shape: IntArray + val elementContext: C + + /** + * Produce a new [N] structure using given initializer function + */ + fun produce(initializer: C.(IntArray) -> T): N + + /** + * Map elements from one structure to another one + */ + fun map(arg: N, transform: C.(T) -> T): N + + /** + * Map indexed elements + */ + fun mapIndexed(arg: N, transform: C.(index: IntArray, T) -> T): N + + /** + * Combine two structures into one + */ + fun combine(a: N, b: N, transform: C.(T, T) -> T): N + + /** + * Check if given elements are consistent with this context + */ + fun check(vararg elements: N) { + elements.forEach { + if (!shape.contentEquals(it.shape)) { + throw ShapeMismatchException(shape, it.shape) + } + } + } + + /** + * element-by-element invoke a function working on [T] on a [NDStructure] + */ + operator fun Function1.invoke(structure: N): N = map(structure) { value -> this@invoke(value) } + + companion object +} + +/** + * An nd-space over element space + */ +interface NDSpace, N : NDStructure> : Space, NDAlgebra { + /** + * Element-by-element addition + */ + override fun add(a: N, b: N): N = combine(a, b) { aValue, bValue -> add(aValue, bValue) } + + /** + * Multiply all elements by constant + */ + override fun multiply(a: N, k: Number): N = map(a) { multiply(it, k) } + + //TODO move to extensions after KEEP-176 + operator fun N.plus(arg: T): N = map(this) { value -> add(arg, value) } + + operator fun N.minus(arg: T): N = map(this) { value -> add(arg, -value) } + + operator fun T.plus(arg: N): N = map(arg) { value -> add(this@plus, value) } + operator fun T.minus(arg: N): N = map(arg) { value -> add(-this@minus, value) } + + companion object +} + +/** + * An nd-ring over element ring + */ +interface NDRing, N : NDStructure> : Ring, NDSpace { + + /** + * Element-by-element multiplication + */ + override fun multiply(a: N, b: N): N = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) } + + //TODO move to extensions after KEEP-176 + operator fun N.times(arg: T): N = map(this) { value -> multiply(arg, value) } + + operator fun T.times(arg: N): N = map(arg) { value -> multiply(this@times, value) } + + companion object +} + +/** + * Field of [NDStructure]. + * + * @param T the type of the element contained in ND structure. + * @param N the type of ND structure. + * @param F field of structure elements. + */ +interface NDField, N : NDStructure> : Field, NDRing { + + /** + * Element-by-element division + */ + override fun divide(a: N, b: N): N = combine(a, b) { aValue, bValue -> divide(aValue, bValue) } + + //TODO move to extensions after KEEP-176 + operator fun N.div(arg: T): N = map(this) { value -> divide(arg, value) } + + operator fun T.div(arg: N): N = map(arg) { divide(it, this@div) } + + companion object { + + private val realNDFieldCache = HashMap() + + /** + * Create a nd-field for [Double] values or pull it from cache if it was created previously + */ + fun real(vararg shape: Int): RealNDField = realNDFieldCache.getOrPut(shape) { RealNDField(shape) } + + /** + * Create a nd-field with boxing generic buffer + */ + fun > boxing( + field: F, + vararg shape: Int, + bufferFactory: BufferFactory = Buffer.Companion::boxing + ): BoxingNDField = BoxingNDField(shape, field, bufferFactory) + + /** + * Create a most suitable implementation for nd-field using reified class. + */ + @Suppress("UNCHECKED_CAST") + inline fun > auto(field: F, vararg shape: Int): BufferedNDField = + when { + T::class == Double::class -> real(*shape) as BufferedNDField + T::class == Complex::class -> complex(*shape) as BufferedNDField + else -> BoxingNDField(shape, field, Buffer.Companion::auto) + } + } +} diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt similarity index 59% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDElement.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index f2f565064..9dfe2b5a8 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -1,9 +1,9 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.Field -import kscience.kmath.operations.RealField -import kscience.kmath.operations.Ring -import kscience.kmath.operations.Space +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.Space /** * The root for all [NDStructure] based algebra elements. Does not implement algebra element root because of problems with recursive self-types @@ -11,41 +11,41 @@ import kscience.kmath.operations.Space * @param C the type of the context for the element * @param N the type of the underlying [NDStructure] */ -public interface NDElement> : NDStructure { - public val context: NDAlgebra +interface NDElement> : NDStructure { - public fun unwrap(): N + val context: NDAlgebra - public fun N.wrap(): NDElement + fun unwrap(): N - public companion object { + fun N.wrap(): NDElement + + companion object { /** * Create a optimized NDArray of doubles */ - public fun real(shape: IntArray, initializer: RealField.(IntArray) -> Double = { 0.0 }): RealNDElement = + fun real(shape: IntArray, initializer: RealField.(IntArray) -> Double = { 0.0 }): RealNDElement = NDField.real(*shape).produce(initializer) - public inline fun real1D(dim: Int, crossinline initializer: (Int) -> Double = { _ -> 0.0 }): RealNDElement = + + fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): RealNDElement = real(intArrayOf(dim)) { initializer(it[0]) } - public inline fun real2D( - dim1: Int, - dim2: Int, - crossinline initializer: (Int, Int) -> Double = { _, _ -> 0.0 } - ): RealNDElement = real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } - public inline fun real3D( + fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): RealNDElement = + real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } + + fun real3D( dim1: Int, dim2: Int, dim3: Int, - crossinline initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 } + initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 } ): RealNDElement = real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } /** * Simple boxing NDArray */ - public fun > boxing( + fun > boxing( shape: IntArray, field: F, initializer: F.(IntArray) -> T @@ -54,7 +54,7 @@ public interface NDElement> : NDStructure { return ndField.produce(initializer) } - public inline fun > auto( + inline fun > auto( shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T @@ -65,16 +65,18 @@ public interface NDElement> : NDStructure { } } -public fun > NDElement.mapIndexed(transform: C.(index: IntArray, T) -> T): NDElement = + +fun > NDElement.mapIndexed(transform: C.(index: IntArray, T) -> T): NDElement = context.mapIndexed(unwrap(), transform).wrap() -public fun > NDElement.map(transform: C.(T) -> T): NDElement = +fun > NDElement.map(transform: C.(T) -> T): NDElement = context.map(unwrap(), transform).wrap() + /** * Element by element application of any operation on elements to the whole [NDElement] */ -public operator fun > Function1.invoke(ndElement: NDElement): NDElement = +operator fun > Function1.invoke(ndElement: NDElement): NDElement = ndElement.map { value -> this@invoke(value) } /* plus and minus */ @@ -82,13 +84,13 @@ public operator fun > Function1.invoke(ndElement: /** * Summation operation for [NDElement] and single element */ -public operator fun , N : NDStructure> NDElement.plus(arg: T): NDElement = +operator fun , N : NDStructure> NDElement.plus(arg: T): NDElement = map { value -> arg + value } /** * Subtraction operation between [NDElement] and single element */ -public operator fun , N : NDStructure> NDElement.minus(arg: T): NDElement = +operator fun , N : NDStructure> NDElement.minus(arg: T): NDElement = map { value -> arg - value } /* prod and div */ @@ -96,15 +98,16 @@ public operator fun , N : NDStructure> NDElement.min /** * Product operation for [NDElement] and single element */ -public operator fun , N : NDStructure> NDElement.times(arg: T): NDElement = +operator fun , N : NDStructure> NDElement.times(arg: T): NDElement = map { value -> arg * value } /** * Division operation between [NDElement] and single element */ -public operator fun , N : NDStructure> NDElement.div(arg: T): NDElement = +operator fun , N : NDStructure> NDElement.div(arg: T): NDElement = map { value -> arg / value } + // /** // * Reverse sum operation // */ diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt similarity index 62% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 08160adf4..9d7735053 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -1,7 +1,6 @@ -package kscience.kmath.structures +package scientifik.kmath.structures import kotlin.jvm.JvmName -import kotlin.native.concurrent.ThreadLocal import kotlin.reflect.KClass /** @@ -11,17 +10,17 @@ import kotlin.reflect.KClass * * @param T the type of items. */ -public interface NDStructure { +interface NDStructure { /** * The shape of structure, i.e. non-empty sequence of non-negative integers that specify sizes of dimensions of * this structure. */ - public val shape: IntArray + val shape: IntArray /** * The count of dimensions in this structure. It should be equal to size of [shape]. */ - public val dimension: Int get() = shape.size + val dimension: Int get() = shape.size /** * Returns the value at the specified indices. @@ -29,28 +28,34 @@ public interface NDStructure { * @param index the indices. * @return the value. */ - public operator fun get(index: IntArray): T + operator fun get(index: IntArray): T /** * Returns the sequence of all the elements associated by their indices. * * @return the lazy sequence of pairs of indices to values. */ - public fun elements(): Sequence> + fun elements(): Sequence> - public override fun equals(other: Any?): Boolean - public override fun hashCode(): Int + override fun equals(other: Any?): Boolean - public companion object { + override fun hashCode(): Int + + companion object { /** * Indicates whether some [NDStructure] is equal to another one. */ - public fun equals(st1: NDStructure<*>, st2: NDStructure<*>): Boolean { + fun equals(st1: NDStructure<*>, st2: NDStructure<*>): Boolean { if (st1 === st2) return true // fast comparison of buffers if possible - if (st1 is NDBuffer && st2 is NDBuffer && st1.strides == st2.strides) + if ( + st1 is NDBuffer && + st2 is NDBuffer && + st1.strides == st2.strides + ) { return st1.buffer.contentEquals(st2.buffer) + } //element by element comparison if it could not be avoided return st1.elements().all { (index, value) -> value == st2[index] } @@ -61,52 +66,52 @@ public interface NDStructure { * * Strides should be reused if possible. */ - public fun build( + fun build( strides: Strides, bufferFactory: BufferFactory = Buffer.Companion::boxing, - initializer: (IntArray) -> T, + initializer: (IntArray) -> T ): BufferNDStructure = BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) /** * Inline create NDStructure with non-boxing buffer implementation if it is possible */ - public inline fun auto( + inline fun auto( strides: Strides, - crossinline initializer: (IntArray) -> T, + crossinline initializer: (IntArray) -> T ): BufferNDStructure = BufferNDStructure(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) - public inline fun auto( + inline fun auto( type: KClass, strides: Strides, - crossinline initializer: (IntArray) -> T, + crossinline initializer: (IntArray) -> T ): BufferNDStructure = BufferNDStructure(strides, Buffer.auto(type, strides.linearSize) { i -> initializer(strides.index(i)) }) - public fun build( + fun build( shape: IntArray, bufferFactory: BufferFactory = Buffer.Companion::boxing, - initializer: (IntArray) -> T, + initializer: (IntArray) -> T ): BufferNDStructure = build(DefaultStrides(shape), bufferFactory, initializer) - public inline fun auto( + inline fun auto( shape: IntArray, - crossinline initializer: (IntArray) -> T, + crossinline initializer: (IntArray) -> T ): BufferNDStructure = auto(DefaultStrides(shape), initializer) @JvmName("autoVarArg") - public inline fun auto( + inline fun auto( vararg shape: Int, - crossinline initializer: (IntArray) -> T, + crossinline initializer: (IntArray) -> T ): BufferNDStructure = auto(DefaultStrides(shape), initializer) - public inline fun auto( + inline fun auto( type: KClass, vararg shape: Int, - crossinline initializer: (IntArray) -> T, + crossinline initializer: (IntArray) -> T ): BufferNDStructure = auto(type, DefaultStrides(shape), initializer) } @@ -118,68 +123,69 @@ public interface NDStructure { * @param index the indices. * @return the value. */ -public operator fun NDStructure.get(vararg index: Int): T = get(index) +operator fun NDStructure.get(vararg index: Int): T = get(index) /** * Represents mutable [NDStructure]. */ -public interface MutableNDStructure : NDStructure { +interface MutableNDStructure : NDStructure { /** * Inserts an item at the specified indices. * * @param index the indices. * @param value the value. */ - public operator fun set(index: IntArray, value: T) + operator fun set(index: IntArray, value: T) } -public inline fun MutableNDStructure.mapInPlace(action: (IntArray, T) -> T): Unit = - elements().forEach { (index, oldValue) -> this[index] = action(index, oldValue) } +inline fun MutableNDStructure.mapInPlace(action: (IntArray, T) -> T) { + elements().forEach { (index, oldValue) -> + this[index] = action(index, oldValue) + } +} /** * A way to convert ND index to linear one and back. */ -public interface Strides { +interface Strides { /** * Shape of NDstructure */ - public val shape: IntArray + val shape: IntArray /** * Array strides */ - public val strides: List + val strides: List /** * Get linear index from multidimensional index */ - public fun offset(index: IntArray): Int + fun offset(index: IntArray): Int /** * Get multidimensional from linear */ - public fun index(offset: Int): IntArray + fun index(offset: Int): IntArray /** * The size of linear buffer to accommodate all elements of ND-structure corresponding to strides */ - public val linearSize: Int - - // TODO introduce a fast way to calculate index of the next element? + val linearSize: Int /** * Iterate over ND indices in a natural order */ - public fun indices(): Sequence = (0 until linearSize).asSequence().map { index(it) } + fun indices(): Sequence { + //TODO introduce a fast way to calculate index of the next element? + return (0 until linearSize).asSequence().map { index(it) } + } } /** * Simple implementation of [Strides]. */ -public class DefaultStrides private constructor(override val shape: IntArray) : Strides { - override val linearSize: Int - get() = strides[shape.size] - +class DefaultStrides private constructor(override val shape: IntArray) : Strides { /** * Strides for memory access */ @@ -187,7 +193,6 @@ public class DefaultStrides private constructor(override val shape: IntArray) : sequence { var current = 1 yield(1) - shape.forEach { current *= it yield(current) @@ -195,27 +200,30 @@ public class DefaultStrides private constructor(override val shape: IntArray) : }.toList() } - override fun offset(index: IntArray): Int = index.mapIndexed { i, value -> - if (value < 0 || value >= this.shape[i]) - throw IndexOutOfBoundsException("Index $value out of shape bounds: (0,${this.shape[i]})") - - value * strides[i] - }.sum() + override fun offset(index: IntArray): Int { + return index.mapIndexed { i, value -> + if (value < 0 || value >= this.shape[i]) { + throw RuntimeException("Index $value out of shape bounds: (0,${this.shape[i]})") + } + value * strides[i] + }.sum() + } override fun index(offset: Int): IntArray { val res = IntArray(shape.size) var current = offset var strideIndex = strides.size - 2 - while (strideIndex >= 0) { res[strideIndex] = (current / strides[strideIndex]) current %= strides[strideIndex] strideIndex-- } - return res } + override val linearSize: Int + get() = strides[shape.size] + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is DefaultStrides) return false @@ -225,15 +233,13 @@ public class DefaultStrides private constructor(override val shape: IntArray) : override fun hashCode(): Int = shape.contentHashCode() - @ThreadLocal - public companion object { + companion object { private val defaultStridesCache = HashMap() /** * Cached builder for default strides */ - public operator fun invoke(shape: IntArray): Strides = - defaultStridesCache.getOrPut(shape) { DefaultStrides(shape) } + operator fun invoke(shape: IntArray): Strides = defaultStridesCache.getOrPut(shape) { DefaultStrides(shape) } } } @@ -242,18 +248,18 @@ public class DefaultStrides private constructor(override val shape: IntArray) : * * @param T the type of items. */ -public abstract class NDBuffer : NDStructure { +abstract class NDBuffer : NDStructure { /** * The underlying buffer. */ - public abstract val buffer: Buffer + abstract val buffer: Buffer /** * The strides to access elements of [Buffer] by linear indices. */ - public abstract val strides: Strides + abstract val strides: Strides - override operator fun get(index: IntArray): T = buffer[strides.offset(index)] + override fun get(index: IntArray): T = buffer[strides.offset(index)] override val shape: IntArray get() = strides.shape @@ -268,30 +274,14 @@ public abstract class NDBuffer : NDStructure { result = 31 * result + buffer.hashCode() return result } - - override fun toString(): String { - val bufferRepr: String = when (shape.size) { - 1 -> buffer.asSequence().joinToString(prefix = "[", postfix = "]", separator = ", ") - 2 -> (0 until shape[0]).joinToString(prefix = "[", postfix = "]", separator = ", ") { i -> - (0 until shape[1]).joinToString(prefix = "[", postfix = "]", separator = ", ") { j -> - val offset = strides.offset(intArrayOf(i, j)) - buffer[offset].toString() - } - } - else -> "..." - } - return "NDBuffer(shape=${shape.contentToString()}, buffer=$bufferRepr)" - } - - } /** * Boxing generic [NDStructure] */ -public class BufferNDStructure( +class BufferNDStructure( override val strides: Strides, - override val buffer: Buffer, + override val buffer: Buffer ) : NDBuffer() { init { if (strides.linearSize != buffer.size) { @@ -303,13 +293,13 @@ public class BufferNDStructure( /** * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferNDStructure] */ -public inline fun NDStructure.mapToBuffer( +inline fun NDStructure.mapToBuffer( factory: BufferFactory = Buffer.Companion::auto, - crossinline transform: (T) -> R, + crossinline transform: (T) -> R ): BufferNDStructure { - return if (this is BufferNDStructure) + return if (this is BufferNDStructure) { BufferNDStructure(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) }) - else { + } else { val strides = DefaultStrides(shape) BufferNDStructure(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) } @@ -318,9 +308,9 @@ public inline fun NDStructure.mapToBuffer( /** * Mutable ND buffer based on linear [MutableBuffer]. */ -public class MutableBufferNDStructure( +class MutableBufferNDStructure( override val strides: Strides, - override val buffer: MutableBuffer, + override val buffer: MutableBuffer ) : NDBuffer(), MutableNDStructure { init { @@ -329,13 +319,13 @@ public class MutableBufferNDStructure( } } - override operator fun set(index: IntArray, value: T): Unit = buffer.set(strides.offset(index), value) + override fun set(index: IntArray, value: T): Unit = buffer.set(strides.offset(index), value) } -public inline fun NDStructure.combine( +inline fun NDStructure.combine( struct: NDStructure, - crossinline block: (T, T) -> T, + crossinline block: (T, T) -> T ): NDStructure { - require(shape.contentEquals(struct.shape)) { "Shape mismatch in structure combination" } + if (!this.shape.contentEquals(struct.shape)) error("Shape mismatch in structure combination") return NDStructure.auto(shape) { block(this[it], struct[it]) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBuffer.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBuffer.kt new file mode 100644 index 000000000..e999e12b2 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBuffer.kt @@ -0,0 +1,49 @@ +package scientifik.kmath.structures + +/** + * Specialized [MutableBuffer] implementation over [DoubleArray]. + * + * @property array the underlying array. + */ +inline class RealBuffer(val array: DoubleArray) : MutableBuffer { + override val size: Int get() = array.size + + override fun get(index: Int): Double = array[index] + + override fun set(index: Int, value: Double) { + array[index] = value + } + + override fun iterator(): DoubleIterator = array.iterator() + + override fun copy(): MutableBuffer = + RealBuffer(array.copyOf()) +} + +/** + * Creates a new [RealBuffer] with the specified [size], where each element is calculated by calling the specified + * [init] function. + * + * The function [init] is called for each array element sequentially starting from the first one. + * It should return the value for an buffer element given its index. + */ +inline fun RealBuffer(size: Int, init: (Int) -> Double): RealBuffer = RealBuffer(DoubleArray(size) { init(it) }) + +/** + * Returns a new [RealBuffer] of given elements. + */ +fun RealBuffer(vararg doubles: Double): RealBuffer = RealBuffer(doubles) + +/** + * Returns a [DoubleArray] containing all of the elements of this [MutableBuffer]. + */ +val MutableBuffer.array: DoubleArray + get() = (if (this is RealBuffer) array else DoubleArray(size) { get(it) }) + +/** + * Returns [RealBuffer] over this array. + * + * @receiver the array. + * @return the new buffer. + */ +fun DoubleArray.asBuffer(): RealBuffer = RealBuffer(this) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBufferField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt similarity index 62% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBufferField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt index f7b2ee31e..a11826e7e 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBufferField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt @@ -1,14 +1,15 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.ExtendedField -import kscience.kmath.operations.ExtendedFieldOperations +import scientifik.kmath.operations.ExtendedField +import scientifik.kmath.operations.ExtendedFieldOperations import kotlin.math.* + /** * [ExtendedFieldOperations] over [RealBuffer]. */ -public object RealBufferFieldOperations : ExtendedFieldOperations> { - public override fun add(a: Buffer, b: Buffer): RealBuffer { +object RealBufferFieldOperations : ExtendedFieldOperations> { + override fun add(a: Buffer, b: Buffer): RealBuffer { require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " } @@ -20,7 +21,7 @@ public object RealBufferFieldOperations : ExtendedFieldOperations } else RealBuffer(DoubleArray(a.size) { a[it] + b[it] }) } - public override fun multiply(a: Buffer, k: Number): RealBuffer { + override fun multiply(a: Buffer, k: Number): RealBuffer { val kValue = k.toDouble() return if (a is RealBuffer) { @@ -29,7 +30,7 @@ public object RealBufferFieldOperations : ExtendedFieldOperations } else RealBuffer(DoubleArray(a.size) { a[it] * kValue }) } - public override fun multiply(a: Buffer, b: Buffer): RealBuffer { + override fun multiply(a: Buffer, b: Buffer): RealBuffer { require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " } @@ -42,7 +43,7 @@ public object RealBufferFieldOperations : ExtendedFieldOperations RealBuffer(DoubleArray(a.size) { a[it] * b[it] }) } - public override fun divide(a: Buffer, b: Buffer): RealBuffer { + override fun divide(a: Buffer, b: Buffer): RealBuffer { require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " } @@ -54,91 +55,84 @@ public object RealBufferFieldOperations : ExtendedFieldOperations } else RealBuffer(DoubleArray(a.size) { a[it] / b[it] }) } - public override fun sin(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun sin(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { sin(array[it]) }) } else RealBuffer(DoubleArray(arg.size) { sin(arg[it]) }) - public override fun cos(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun cos(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { cos(array[it]) }) } else RealBuffer(DoubleArray(arg.size) { cos(arg[it]) }) - public override fun tan(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun tan(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { tan(array[it]) }) } else RealBuffer(DoubleArray(arg.size) { tan(arg[it]) }) - public override fun asin(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun asin(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { asin(array[it]) }) - } else + } else { RealBuffer(DoubleArray(arg.size) { asin(arg[it]) }) + } - public override fun acos(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun acos(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { acos(array[it]) }) } else RealBuffer(DoubleArray(arg.size) { acos(arg[it]) }) - public override fun atan(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun atan(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { atan(array[it]) }) } else RealBuffer(DoubleArray(arg.size) { atan(arg[it]) }) - public override fun sinh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun sinh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { sinh(array[it]) }) - } else - RealBuffer(DoubleArray(arg.size) { sinh(arg[it]) }) + } else RealBuffer(DoubleArray(arg.size) { sinh(arg[it]) }) - public override fun cosh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun cosh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { cosh(array[it]) }) - } else - RealBuffer(DoubleArray(arg.size) { cosh(arg[it]) }) + } else RealBuffer(DoubleArray(arg.size) { cosh(arg[it]) }) - public override fun tanh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun tanh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { tanh(array[it]) }) - } else - RealBuffer(DoubleArray(arg.size) { tanh(arg[it]) }) + } else RealBuffer(DoubleArray(arg.size) { tanh(arg[it]) }) - public override fun asinh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun asinh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { asinh(array[it]) }) - } else - RealBuffer(DoubleArray(arg.size) { asinh(arg[it]) }) + } else RealBuffer(DoubleArray(arg.size) { asinh(arg[it]) }) - public override fun acosh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun acosh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { acosh(array[it]) }) - } else - RealBuffer(DoubleArray(arg.size) { acosh(arg[it]) }) + } else RealBuffer(DoubleArray(arg.size) { acosh(arg[it]) }) - public override fun atanh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun atanh(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { atanh(array[it]) }) - } else - RealBuffer(DoubleArray(arg.size) { atanh(arg[it]) }) + } else RealBuffer(DoubleArray(arg.size) { atanh(arg[it]) }) - public override fun power(arg: Buffer, pow: Number): RealBuffer = if (arg is RealBuffer) { + override fun power(arg: Buffer, pow: Number): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { array[it].pow(pow.toDouble()) }) - } else - RealBuffer(DoubleArray(arg.size) { arg[it].pow(pow.toDouble()) }) + } else RealBuffer(DoubleArray(arg.size) { arg[it].pow(pow.toDouble()) }) - public override fun exp(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun exp(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { exp(array[it]) }) } else RealBuffer(DoubleArray(arg.size) { exp(arg[it]) }) - public override fun ln(arg: Buffer): RealBuffer = if (arg is RealBuffer) { + override fun ln(arg: Buffer): RealBuffer = if (arg is RealBuffer) { val array = arg.array RealBuffer(DoubleArray(arg.size) { ln(array[it]) }) - } else - RealBuffer(DoubleArray(arg.size) { ln(arg[it]) }) + } else RealBuffer(DoubleArray(arg.size) { ln(arg[it]) }) } /** @@ -146,101 +140,101 @@ public object RealBufferFieldOperations : ExtendedFieldOperations * * @property size the size of buffers to operate on. */ -public class RealBufferField(public val size: Int) : ExtendedField> { - public override val zero: Buffer by lazy { RealBuffer(size) { 0.0 } } - public override val one: Buffer by lazy { RealBuffer(size) { 1.0 } } +class RealBufferField(val size: Int) : ExtendedField> { + override val zero: Buffer by lazy { RealBuffer(size) { 0.0 } } + override val one: Buffer by lazy { RealBuffer(size) { 1.0 } } - public override fun add(a: Buffer, b: Buffer): RealBuffer { + override fun add(a: Buffer, b: Buffer): RealBuffer { require(a.size == size) { "The buffer size ${a.size} does not match context size $size" } return RealBufferFieldOperations.add(a, b) } - public override fun multiply(a: Buffer, k: Number): RealBuffer { + override fun multiply(a: Buffer, k: Number): RealBuffer { require(a.size == size) { "The buffer size ${a.size} does not match context size $size" } return RealBufferFieldOperations.multiply(a, k) } - public override fun multiply(a: Buffer, b: Buffer): RealBuffer { + override fun multiply(a: Buffer, b: Buffer): RealBuffer { require(a.size == size) { "The buffer size ${a.size} does not match context size $size" } return RealBufferFieldOperations.multiply(a, b) } - public override fun divide(a: Buffer, b: Buffer): RealBuffer { + override fun divide(a: Buffer, b: Buffer): RealBuffer { require(a.size == size) { "The buffer size ${a.size} does not match context size $size" } return RealBufferFieldOperations.divide(a, b) } - public override fun sin(arg: Buffer): RealBuffer { + override fun sin(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.sin(arg) } - public override fun cos(arg: Buffer): RealBuffer { + override fun cos(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.cos(arg) } - public override fun tan(arg: Buffer): RealBuffer { + override fun tan(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.tan(arg) } - public override fun asin(arg: Buffer): RealBuffer { + override fun asin(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.asin(arg) } - public override fun acos(arg: Buffer): RealBuffer { + override fun acos(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.acos(arg) } - public override fun atan(arg: Buffer): RealBuffer { + override fun atan(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.atan(arg) } - public override fun sinh(arg: Buffer): RealBuffer { + override fun sinh(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.sinh(arg) } - public override fun cosh(arg: Buffer): RealBuffer { + override fun cosh(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.cosh(arg) } - public override fun tanh(arg: Buffer): RealBuffer { + override fun tanh(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.tanh(arg) } - public override fun asinh(arg: Buffer): RealBuffer { + override fun asinh(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.asinh(arg) } - public override fun acosh(arg: Buffer): RealBuffer { + override fun acosh(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.acosh(arg) } - public override fun atanh(arg: Buffer): RealBuffer { + override fun atanh(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.atanh(arg) } - public override fun power(arg: Buffer, pow: Number): RealBuffer { + override fun power(arg: Buffer, pow: Number): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.power(arg, pow) } - public override fun exp(arg: Buffer): RealBuffer { + override fun exp(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.exp(arg) } - public override fun ln(arg: Buffer): RealBuffer { + override fun ln(arg: Buffer): RealBuffer { require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } return RealBufferFieldOperations.ln(arg) } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt similarity index 79% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealNDField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index ed28fb9f2..6533f64be 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -1,11 +1,11 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.FieldElement -import kscience.kmath.operations.RealField +import scientifik.kmath.operations.FieldElement +import scientifik.kmath.operations.RealField -public typealias RealNDElement = BufferedNDFieldElement +typealias RealNDElement = BufferedNDFieldElement -public class RealNDField(override val shape: IntArray) : +class RealNDField(override val shape: IntArray) : BufferedNDField, ExtendedNDField> { @@ -15,7 +15,7 @@ public class RealNDField(override val shape: IntArray) : override val zero: RealNDElement by lazy { produce { zero } } override val one: RealNDElement by lazy { produce { one } } - public inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer = + inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer = RealBuffer(DoubleArray(size) { initializer(it) }) /** @@ -90,7 +90,7 @@ public class RealNDField(override val shape: IntArray) : /** * Fast element production using function inlining */ -public inline fun BufferedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { +inline fun BufferedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) } return BufferedNDFieldElement(this, RealBuffer(array)) } @@ -98,13 +98,13 @@ public inline fun BufferedNDField.produceInline(crossinline i /** * Map one [RealNDElement] using function with indices. */ -public inline fun RealNDElement.mapIndexed(crossinline transform: RealField.(index: IntArray, Double) -> Double): RealNDElement = +inline fun RealNDElement.mapIndexed(crossinline transform: RealField.(index: IntArray, Double) -> Double): RealNDElement = context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) } /** * Map one [RealNDElement] using function without indices. */ -public inline fun RealNDElement.map(crossinline transform: RealField.(Double) -> Double): RealNDElement { +inline fun RealNDElement.map(crossinline transform: RealField.(Double) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> RealField.transform(buffer[offset]) } return BufferedNDFieldElement(context, RealBuffer(array)) } @@ -112,22 +112,26 @@ public inline fun RealNDElement.map(crossinline transform: RealField.(Double) -> /** * Element by element application of any operation on elements to the whole array. Just like in numpy. */ -public operator fun Function1.invoke(ndElement: RealNDElement): RealNDElement = +operator fun Function1.invoke(ndElement: RealNDElement): RealNDElement = ndElement.map { this@invoke(it) } + /* plus and minus */ /** * Summation operation for [BufferedNDElement] and single element */ -public operator fun RealNDElement.plus(arg: Double): RealNDElement = map { it + arg } +operator fun RealNDElement.plus(arg: Double): RealNDElement = + map { it + arg } /** * Subtraction operation between [BufferedNDElement] and single element */ -public operator fun RealNDElement.minus(arg: Double): RealNDElement = map { it - arg } +operator fun RealNDElement.minus(arg: Double): RealNDElement = + map { it - arg } /** * Produce a context for n-dimensional operations inside this real field */ -public inline fun RealField.nd(vararg shape: Int, action: RealNDField.() -> R): R = NDField.real(*shape).run(action) + +inline fun RealField.nd(vararg shape: Int, action: RealNDField.() -> R): R = NDField.real(*shape).run(action) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ShortBuffer.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortBuffer.kt similarity index 50% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ShortBuffer.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortBuffer.kt index 0d9222320..c6f19feaf 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ShortBuffer.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortBuffer.kt @@ -1,21 +1,24 @@ -package kscience.kmath.structures +package scientifik.kmath.structures /** * Specialized [MutableBuffer] implementation over [ShortArray]. * * @property array the underlying array. */ -public inline class ShortBuffer(public val array: ShortArray) : MutableBuffer { - public override val size: Int get() = array.size +inline class ShortBuffer(val array: ShortArray) : MutableBuffer { + override val size: Int get() = array.size - public override operator fun get(index: Int): Short = array[index] + override fun get(index: Int): Short = array[index] - public override operator fun set(index: Int, value: Short) { + override fun set(index: Int, value: Short) { array[index] = value } - public override operator fun iterator(): ShortIterator = array.iterator() - public override fun copy(): MutableBuffer = ShortBuffer(array.copyOf()) + override fun iterator(): ShortIterator = array.iterator() + + override fun copy(): MutableBuffer = + ShortBuffer(array.copyOf()) + } /** @@ -25,17 +28,17 @@ public inline class ShortBuffer(public val array: ShortArray) : MutableBuffer Short): ShortBuffer = ShortBuffer(ShortArray(size) { init(it) }) +inline fun ShortBuffer(size: Int, init: (Int) -> Short): ShortBuffer = ShortBuffer(ShortArray(size) { init(it) }) /** * Returns a new [ShortBuffer] of given elements. */ -public fun ShortBuffer(vararg shorts: Short): ShortBuffer = ShortBuffer(shorts) +fun ShortBuffer(vararg shorts: Short): ShortBuffer = ShortBuffer(shorts) /** * Returns a [ShortArray] containing all of the elements of this [MutableBuffer]. */ -public val MutableBuffer.array: ShortArray +val MutableBuffer.array: ShortArray get() = (if (this is ShortBuffer) array else ShortArray(size) { get(it) }) /** @@ -44,4 +47,4 @@ public val MutableBuffer.array: ShortArray * @receiver the array. * @return the new buffer. */ -public fun ShortArray.asBuffer(): ShortBuffer = ShortBuffer(this) +fun ShortArray.asBuffer(): ShortBuffer = ShortBuffer(this) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ShortNDRing.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt similarity index 74% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ShortNDRing.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt index 3b506a26a..f404a2a27 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/ShortNDRing.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt @@ -1,19 +1,21 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.RingElement -import kscience.kmath.operations.ShortRing +import scientifik.kmath.operations.RingElement +import scientifik.kmath.operations.ShortRing -public typealias ShortNDElement = BufferedNDRingElement -public class ShortNDRing(override val shape: IntArray) : +typealias ShortNDElement = BufferedNDRingElement + +class ShortNDRing(override val shape: IntArray) : BufferedNDRing { override val strides: Strides = DefaultStrides(shape) + override val elementContext: ShortRing get() = ShortRing override val zero: ShortNDElement by lazy { produce { zero } } override val one: ShortNDElement by lazy { produce { one } } - public inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = + inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = ShortBuffer(ShortArray(size) { initializer(it) }) /** @@ -68,13 +70,15 @@ public class ShortNDRing(override val shape: IntArray) : /** * Fast element production using function inlining. */ -public inline fun BufferedNDRing.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement = - BufferedNDRingElement(this, ShortBuffer(ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) })) +inline fun BufferedNDRing.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement { + val array = ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) } + return BufferedNDRingElement(this, ShortBuffer(array)) +} /** * Element by element application of any operation on elements to the whole array. */ -public operator fun Function1.invoke(ndElement: ShortNDElement): ShortNDElement = +operator fun Function1.invoke(ndElement: ShortNDElement): ShortNDElement = ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } @@ -83,11 +87,11 @@ public operator fun Function1.invoke(ndElement: ShortNDElement): S /** * Summation operation for [ShortNDElement] and single element. */ -public operator fun ShortNDElement.plus(arg: Short): ShortNDElement = +operator fun ShortNDElement.plus(arg: Short): ShortNDElement = context.produceInline { i -> (buffer[i] + arg).toShort() } /** * Subtraction operation between [ShortNDElement] and single element. */ -public operator fun ShortNDElement.minus(arg: Short): ShortNDElement = +operator fun ShortNDElement.minus(arg: Short): ShortNDElement = context.produceInline { i -> (buffer[i] - arg).toShort() } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure1D.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt similarity index 56% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure1D.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt index 95422ac60..faf022367 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/Structure1D.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt @@ -1,27 +1,29 @@ -package kscience.kmath.structures +package scientifik.kmath.structures /** * A structure that is guaranteed to be one-dimensional */ -public interface Structure1D : NDStructure, Buffer { - public override val dimension: Int get() = 1 +interface Structure1D : NDStructure, Buffer { + override val dimension: Int get() = 1 - public override operator fun get(index: IntArray): T { - require(index.size == 1) { "Index dimension mismatch. Expected 1 but found ${index.size}" } + override fun get(index: IntArray): T { + if (index.size != 1) error("Index dimension mismatch. Expected 1 but found ${index.size}") return get(index[0]) } - public override operator fun iterator(): Iterator = (0 until size).asSequence().map(::get).iterator() + override fun iterator(): Iterator = (0 until size).asSequence().map { get(it) }.iterator() } /** * A 1D wrapper for nd-structure */ private inline class Structure1DWrapper(val structure: NDStructure) : Structure1D { + override val shape: IntArray get() = structure.shape override val size: Int get() = structure.shape[0] - override operator fun get(index: Int): T = structure[index] + override fun get(index: Int): T = structure[index] + override fun elements(): Sequence> = structure.elements() } @@ -31,23 +33,30 @@ private inline class Structure1DWrapper(val structure: NDStructure) : Stru */ private inline class Buffer1DWrapper(val buffer: Buffer) : Structure1D { override val shape: IntArray get() = intArrayOf(buffer.size) + override val size: Int get() = buffer.size override fun elements(): Sequence> = asSequence().mapIndexed { index, value -> intArrayOf(index) to value } - override operator fun get(index: Int): T = buffer[index] + override fun get(index: Int): T = buffer[index] } /** * Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch */ -public fun NDStructure.as1D(): Structure1D = if (shape.size == 1) { - if (this is NDBuffer) Buffer1DWrapper(this.buffer) else Structure1DWrapper(this) -} else +fun NDStructure.as1D(): Structure1D = if (shape.size == 1) { + if (this is NDBuffer) { + Buffer1DWrapper(this.buffer) + } else { + Structure1DWrapper(this) + } +} else { error("Can't create 1d-structure from ${shape.size}d-structure") +} + /** * Represent this buffer as 1D structure */ -public fun Buffer.asND(): Structure1D = Buffer1DWrapper(this) +fun Buffer.asND(): Structure1D = Buffer1DWrapper(this) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure2D.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure2D.kt new file mode 100644 index 000000000..30fd556d3 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure2D.kt @@ -0,0 +1,58 @@ +package scientifik.kmath.structures + +/** + * A structure that is guaranteed to be two-dimensional + */ +interface Structure2D : NDStructure { + val rowNum: Int get() = shape[0] + val colNum: Int get() = shape[1] + + operator fun get(i: Int, j: Int): T + + override fun get(index: IntArray): T { + if (index.size != 2) error("Index dimension mismatch. Expected 2 but found ${index.size}") + return get(index[0], index[1]) + } + + val rows: Buffer> + get() = VirtualBuffer(rowNum) { i -> + VirtualBuffer(colNum) { j -> get(i, j) } + } + + val columns: Buffer> + get() = VirtualBuffer(colNum) { j -> + VirtualBuffer(rowNum) { i -> get(i, j) } + } + + override fun elements(): Sequence> = sequence { + for (i in (0 until rowNum)) { + for (j in (0 until colNum)) { + yield(intArrayOf(i, j) to get(i, j)) + } + } + } + + companion object +} + +/** + * A 2D wrapper for nd-structure + */ +private inline class Structure2DWrapper(val structure: NDStructure) : Structure2D { + override fun get(i: Int, j: Int): T = structure[i, j] + + override val shape: IntArray get() = structure.shape + + override fun elements(): Sequence> = structure.elements() +} + +/** + * Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch + */ +fun NDStructure.as2D(): Structure2D = if (shape.size == 2) { + Structure2DWrapper(this) +} else { + error("Can't create 2d-structure from ${shape.size}d-structure") +} + +typealias Matrix = Structure2D diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/expressions/SimpleAutoDiffTest.kt b/kmath-core/src/commonTest/kotlin/kscience/kmath/expressions/SimpleAutoDiffTest.kt deleted file mode 100644 index 510ed23a9..000000000 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/expressions/SimpleAutoDiffTest.kt +++ /dev/null @@ -1,285 +0,0 @@ -package kscience.kmath.expressions - -import kscience.kmath.operations.RealField -import kscience.kmath.structures.asBuffer -import kotlin.math.E -import kotlin.math.PI -import kotlin.math.pow -import kotlin.math.sqrt -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class SimpleAutoDiffTest { - - fun dx( - xBinding: Pair, - body: SimpleAutoDiffField.(x: AutoDiffValue) -> AutoDiffValue, - ): DerivationResult = RealField.simpleAutoDiff(xBinding) { body(bind(xBinding.first)) } - - fun dxy( - xBinding: Pair, - yBinding: Pair, - body: SimpleAutoDiffField.(x: AutoDiffValue, y: AutoDiffValue) -> AutoDiffValue, - ): DerivationResult = RealField.simpleAutoDiff(xBinding, yBinding) { - body(bind(xBinding.first), bind(yBinding.first)) - } - - fun diff(block: SimpleAutoDiffField.() -> AutoDiffValue): SimpleAutoDiffExpression { - return SimpleAutoDiffExpression(RealField, block) - } - - val x by symbol - val y by symbol - val z by symbol - - @Test - fun testPlusX2() { - val y = RealField.simpleAutoDiff(x to 3.0) { - // diff w.r.t this x at 3 - val x = bind(x) - x + x - } - assertEquals(6.0, y.value) // y = x + x = 6 - assertEquals(2.0, y.derivative(x)) // dy/dx = 2 - } - - @Test - fun testPlusX2Expr() { - val expr = diff { - val x = bind(x) - x + x - } - assertEquals(6.0, expr(x to 3.0)) // y = x + x = 6 - assertEquals(2.0, expr.derivative(x)(x to 3.0)) // dy/dx = 2 - } - - - @Test - fun testPlus() { - // two variables - val z = RealField.simpleAutoDiff(x to 2.0, y to 3.0) { - val x = bind(x) - val y = bind(y) - x + y - } - assertEquals(5.0, z.value) // z = x + y = 5 - assertEquals(1.0, z.derivative(x)) // dz/dx = 1 - assertEquals(1.0, z.derivative(y)) // dz/dy = 1 - } - - @Test - fun testMinus() { - // two variables - val z = RealField.simpleAutoDiff(x to 7.0, y to 3.0) { - val x = bind(x) - val y = bind(y) - - x - y - } - assertEquals(4.0, z.value) // z = x - y = 4 - assertEquals(1.0, z.derivative(x)) // dz/dx = 1 - assertEquals(-1.0, z.derivative(y)) // dz/dy = -1 - } - - @Test - fun testMulX2() { - val y = dx(x to 3.0) { x -> - // diff w.r.t this x at 3 - x * x - } - assertEquals(9.0, y.value) // y = x * x = 9 - assertEquals(6.0, y.derivative(x)) // dy/dx = 2 * x = 7 - } - - @Test - fun testSqr() { - val y = dx(x to 3.0) { x -> sqr(x) } - assertEquals(9.0, y.value) // y = x ^ 2 = 9 - assertEquals(6.0, y.derivative(x)) // dy/dx = 2 * x = 7 - } - - @Test - fun testSqrSqr() { - val y = dx(x to 2.0) { x -> sqr(sqr(x)) } - assertEquals(16.0, y.value) // y = x ^ 4 = 16 - assertEquals(32.0, y.derivative(x)) // dy/dx = 4 * x^3 = 32 - } - - @Test - fun testX3() { - val y = dx(x to 2.0) { x -> - // diff w.r.t this x at 2 - x * x * x - } - assertEquals(8.0, y.value) // y = x * x * x = 8 - assertEquals(12.0, y.derivative(x)) // dy/dx = 3 * x * x = 12 - } - - @Test - fun testDiv() { - val z = dxy(x to 5.0, y to 2.0) { x, y -> - x / y - } - assertEquals(2.5, z.value) // z = x / y = 2.5 - assertEquals(0.5, z.derivative(x)) // dz/dx = 1 / y = 0.5 - assertEquals(-1.25, z.derivative(y)) // dz/dy = -x / y^2 = -1.25 - } - - @Test - fun testPow3() { - val y = dx(x to 2.0) { x -> - // diff w.r.t this x at 2 - pow(x, 3) - } - assertEquals(8.0, y.value) // y = x ^ 3 = 8 - assertEquals(12.0, y.derivative(x)) // dy/dx = 3 * x ^ 2 = 12 - } - - @Test - fun testPowFull() { - val z = dxy(x to 2.0, y to 3.0) { x, y -> - pow(x, y) - } - assertApprox(8.0, z.value) // z = x ^ y = 8 - assertApprox(12.0, z.derivative(x)) // dz/dx = y * x ^ (y - 1) = 12 - assertApprox(8.0 * kotlin.math.ln(2.0), z.derivative(y)) // dz/dy = x ^ y * ln(x) - } - - @Test - fun testFromPaper() { - val y = dx(x to 3.0) { x -> 2 * x + x * x * x } - assertEquals(33.0, y.value) // y = 2 * x + x * x * x = 33 - assertEquals(29.0, y.derivative(x)) // dy/dx = 2 + 3 * x * x = 29 - } - - @Test - fun testInnerVariable() { - val y = dx(x to 1.0) { x -> - const(1.0) * x - } - assertEquals(1.0, y.value) // y = x ^ n = 1 - assertEquals(1.0, y.derivative(x)) // dy/dx = n * x ^ (n - 1) = n - 1 - } - - @Test - fun testLongChain() { - val n = 10_000 - val y = dx(x to 1.0) { x -> - var res = const(1.0) - for (i in 1..n) res *= x - res - } - assertEquals(1.0, y.value) // y = x ^ n = 1 - assertEquals(n.toDouble(), y.derivative(x)) // dy/dx = n * x ^ (n - 1) = n - 1 - } - - @Test - fun testExample() { - val y = dx(x to 2.0) { x -> sqr(x) + 5 * x + 3 } - assertEquals(17.0, y.value) // the value of result (y) - assertEquals(9.0, y.derivative(x)) // dy/dx - } - - @Test - fun testSqrt() { - val y = dx(x to 16.0) { x -> sqrt(x) } - assertEquals(4.0, y.value) // y = x ^ 1/2 = 4 - assertEquals(1.0 / 8, y.derivative(x)) // dy/dx = 1/2 / x ^ 1/4 = 1/8 - } - - @Test - fun testSin() { - val y = dx(x to PI / 6.0) { x -> sin(x) } - assertApprox(0.5, y.value) // y = sin(PI/6) = 0.5 - assertApprox(sqrt(3.0) / 2, y.derivative(x)) // dy/dx = cos(pi/6) = sqrt(3)/2 - } - - @Test - fun testCos() { - val y = dx(x to PI / 6) { x -> cos(x) } - assertApprox(sqrt(3.0) / 2, y.value) //y = cos(pi/6) = sqrt(3)/2 - assertApprox(-0.5, y.derivative(x)) // dy/dx = -sin(pi/6) = -0.5 - } - - @Test - fun testTan() { - val y = dx(x to PI / 6) { x -> tan(x) } - assertApprox(1.0 / sqrt(3.0), y.value) // y = tan(pi/6) = 1/sqrt(3) - assertApprox(4.0 / 3.0, y.derivative(x)) // dy/dx = sec(pi/6)^2 = 4/3 - } - - @Test - fun testAsin() { - val y = dx(x to PI / 6) { x -> asin(x) } - assertApprox(kotlin.math.asin(PI / 6.0), y.value) // y = asin(pi/6) - assertApprox(6.0 / sqrt(36 - PI * PI), y.derivative(x)) // dy/dx = 6/sqrt(36-pi^2) - } - - @Test - fun testAcos() { - val y = dx(x to PI / 6) { x -> acos(x) } - assertApprox(kotlin.math.acos(PI / 6.0), y.value) // y = acos(pi/6) - assertApprox(-6.0 / sqrt(36.0 - PI * PI), y.derivative(x)) // dy/dx = -6/sqrt(36-pi^2) - } - - @Test - fun testAtan() { - val y = dx(x to PI / 6) { x -> atan(x) } - assertApprox(kotlin.math.atan(PI / 6.0), y.value) // y = atan(pi/6) - assertApprox(36.0 / (36.0 + PI * PI), y.derivative(x)) // dy/dx = 36/(36+pi^2) - } - - @Test - fun testSinh() { - val y = dx(x to 0.0) { x -> sinh(x) } - assertApprox(kotlin.math.sinh(0.0), y.value) // y = sinh(0) - assertApprox(kotlin.math.cosh(0.0), y.derivative(x)) // dy/dx = cosh(0) - } - - @Test - fun testCosh() { - val y = dx(x to 0.0) { x -> cosh(x) } - assertApprox(1.0, y.value) //y = cosh(0) - assertApprox(0.0, y.derivative(x)) // dy/dx = sinh(0) - } - - @Test - fun testTanh() { - val y = dx(x to 1.0) { x -> tanh(x) } - assertApprox((E * E - 1) / (E * E + 1), y.value) // y = tanh(pi/6) - assertApprox(1.0 / kotlin.math.cosh(1.0).pow(2), y.derivative(x)) // dy/dx = sech(pi/6)^2 - } - - @Test - fun testAsinh() { - val y = dx(x to PI / 6) { x -> asinh(x) } - assertApprox(kotlin.math.asinh(PI / 6.0), y.value) // y = asinh(pi/6) - assertApprox(6.0 / sqrt(36 + PI * PI), y.derivative(x)) // dy/dx = 6/sqrt(pi^2+36) - } - - @Test - fun testAcosh() { - val y = dx(x to PI / 6) { x -> acosh(x) } - assertApprox(kotlin.math.acosh(PI / 6.0), y.value) // y = acosh(pi/6) - assertApprox(-6.0 / sqrt(36.0 - PI * PI), y.derivative(x)) // dy/dx = -6/sqrt(36-pi^2) - } - - @Test - fun testAtanh() { - val y = dx(x to PI / 6) { x -> atanh(x) } - assertApprox(kotlin.math.atanh(PI / 6.0), y.value) // y = atanh(pi/6) - assertApprox(-36.0 / (PI * PI - 36.0), y.derivative(x)) // dy/dx = -36/(pi^2-36) - } - - @Test - fun testDivGrad() { - val res = dxy(x to 1.0, y to 2.0) { x, y -> x * x + y * y } - assertEquals(6.0, res.div()) - assertTrue(res.grad(x, y).contentEquals(doubleArrayOf(2.0, 4.0).asBuffer())) - } - - private fun assertApprox(a: Double, b: Double) { - if ((a - b) > 1e-10) assertEquals(a, b) - } -} diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/expressions/ExpressionFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt similarity index 53% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/expressions/ExpressionFieldTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt index 484993eef..22b924ef9 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/expressions/ExpressionFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt @@ -1,60 +1,53 @@ -package kscience.kmath.expressions +package scientifik.kmath.expressions -import kscience.kmath.operations.Complex -import kscience.kmath.operations.ComplexField -import kscience.kmath.operations.RealField -import kscience.kmath.operations.invoke +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.ComplexField +import scientifik.kmath.operations.RealField import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertFails class ExpressionFieldTest { - val x by symbol @Test fun testExpression() { val context = FunctionalExpressionField(RealField) - - val expression = context { - val x by binding() + val expression = with(context) { + val x = variable("x", 2.0) x * x + 2 * x + one } - - assertEquals(expression(x to 1.0), 4.0) - assertFails { expression()} + assertEquals(expression("x" to 1.0), 4.0) + assertEquals(expression(), 9.0) } @Test fun testComplex() { val context = FunctionalExpressionField(ComplexField) - - val expression = context { - val x = bind(x) + val expression = with(context) { + val x = variable("x", Complex(2.0, 0.0)) x * x + 2 * x + one } - - assertEquals(expression(x to Complex(1.0, 0.0)), Complex(4.0, 0.0)) - //assertEquals(expression(), Complex(9.0, 0.0)) + assertEquals(expression("x" to Complex(1.0, 0.0)), Complex(4.0, 0.0)) + assertEquals(expression(), Complex(9.0, 0.0)) } @Test fun separateContext() { fun FunctionalExpressionField.expression(): Expression { - val x by binding() + val x = variable("x") return x * x + 2 * x + one } val expression = FunctionalExpressionField(RealField).expression() - assertEquals(expression(x to 1.0), 4.0) + assertEquals(expression("x" to 1.0), 4.0) } @Test fun valueExpression() { val expressionBuilder: FunctionalExpressionField.() -> Expression = { - val x by binding() + val x = variable("x") x * x + 2 * x + one } val expression = FunctionalExpressionField(RealField).expressionBuilder() - assertEquals(expression(x to 1.0), 4.0) + assertEquals(expression("x" to 1.0), 4.0) } } diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt similarity index 86% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/linear/MatrixTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index 0a582e339..987426250 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -1,13 +1,13 @@ -package kscience.kmath.linear +package scientifik.kmath.linear -import kscience.kmath.operations.invoke -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.NDStructure -import kscience.kmath.structures.as2D +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.NDStructure +import scientifik.kmath.structures.as2D import kotlin.test.Test import kotlin.test.assertEquals class MatrixTest { + @Test fun testTranspose() { val matrix = MatrixContext.real.one(3, 3) @@ -39,7 +39,7 @@ class MatrixTest { infix fun Matrix.pow(power: Int): Matrix { var res = this repeat(power - 1) { - res = RealMatrixContext.invoke { res dot this@pow } + res = res dot this } return res } @@ -51,7 +51,6 @@ class MatrixTest { fun test2DDot() { val firstMatrix = NDStructure.auto(2, 3) { (i, j) -> (i + j).toDouble() }.as2D() val secondMatrix = NDStructure.auto(3, 2) { (i, j) -> (i + j).toDouble() }.as2D() - MatrixContext.real.run { // val firstMatrix = produce(2, 3) { i, j -> (i + j).toDouble() } // val secondMatrix = produce(3, 2) { i, j -> (i + j).toDouble() } diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt similarity index 76% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/linear/RealLUSolverTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index 28dfe46ec..34bd8a0e3 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -1,15 +1,17 @@ -package kscience.kmath.linear +package scientifik.kmath.linear -import kscience.kmath.structures.Matrix +import scientifik.kmath.structures.Matrix +import kotlin.contracts.ExperimentalContracts import kotlin.test.Test import kotlin.test.assertEquals +@ExperimentalContracts class RealLUSolverTest { @Test fun testInvertOne() { val matrix = MatrixContext.real.one(2, 2) - val inverted = MatrixContext.real.inverseWithLUP(matrix) + val inverted = MatrixContext.real.inverse(matrix) assertEquals(matrix, inverted) } @@ -37,7 +39,7 @@ class RealLUSolverTest { 1.0, 3.0 ) - val inverted = MatrixContext.real.inverseWithLUP(matrix) + val inverted = MatrixContext.real.inverse(matrix) val expected = Matrix.square( 0.375, -0.125, diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/linear/VectorSpaceTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/VectorSpaceTest.kt similarity index 100% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/linear/VectorSpaceTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/VectorSpaceTest.kt diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/AutoDiffTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/AutoDiffTest.kt new file mode 100644 index 000000000..c08a63ccb --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/AutoDiffTest.kt @@ -0,0 +1,181 @@ +package scientifik.kmath.misc + +import scientifik.kmath.operations.RealField +import scientifik.kmath.structures.asBuffer +import kotlin.math.PI +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class AutoDiffTest { + fun Variable(int: Int): Variable = Variable(int.toDouble()) + + fun deriv(body: AutoDiffField.() -> Variable): DerivationResult = + RealField.deriv(body) + + @Test + fun testPlusX2() { + val x = Variable(3) // diff w.r.t this x at 3 + val y = deriv { x + x } + assertEquals(6.0, y.value) // y = x + x = 6 + assertEquals(2.0, y.deriv(x)) // dy/dx = 2 + } + + @Test + fun testPlus() { + // two variables + val x = Variable(2) + val y = Variable(3) + val z = deriv { x + y } + assertEquals(5.0, z.value) // z = x + y = 5 + assertEquals(1.0, z.deriv(x)) // dz/dx = 1 + assertEquals(1.0, z.deriv(y)) // dz/dy = 1 + } + + @Test + fun testMinus() { + // two variables + val x = Variable(7) + val y = Variable(3) + val z = deriv { x - y } + assertEquals(4.0, z.value) // z = x - y = 4 + assertEquals(1.0, z.deriv(x)) // dz/dx = 1 + assertEquals(-1.0, z.deriv(y)) // dz/dy = -1 + } + + @Test + fun testMulX2() { + val x = Variable(3) // diff w.r.t this x at 3 + val y = deriv { x * x } + assertEquals(9.0, y.value) // y = x * x = 9 + assertEquals(6.0, y.deriv(x)) // dy/dx = 2 * x = 7 + } + + @Test + fun testSqr() { + val x = Variable(3) + val y = deriv { sqr(x) } + assertEquals(9.0, y.value) // y = x ^ 2 = 9 + assertEquals(6.0, y.deriv(x)) // dy/dx = 2 * x = 7 + } + + @Test + fun testSqrSqr() { + val x = Variable(2) + val y = deriv { sqr(sqr(x)) } + assertEquals(16.0, y.value) // y = x ^ 4 = 16 + assertEquals(32.0, y.deriv(x)) // dy/dx = 4 * x^3 = 32 + } + + @Test + fun testX3() { + val x = Variable(2) // diff w.r.t this x at 2 + val y = deriv { x * x * x } + assertEquals(8.0, y.value) // y = x * x * x = 8 + assertEquals(12.0, y.deriv(x)) // dy/dx = 3 * x * x = 12 + } + + @Test + fun testDiv() { + val x = Variable(5) + val y = Variable(2) + val z = deriv { x / y } + assertEquals(2.5, z.value) // z = x / y = 2.5 + assertEquals(0.5, z.deriv(x)) // dz/dx = 1 / y = 0.5 + assertEquals(-1.25, z.deriv(y)) // dz/dy = -x / y^2 = -1.25 + } + + @Test + fun testPow3() { + val x = Variable(2) // diff w.r.t this x at 2 + val y = deriv { pow(x, 3) } + assertEquals(8.0, y.value) // y = x ^ 3 = 8 + assertEquals(12.0, y.deriv(x)) // dy/dx = 3 * x ^ 2 = 12 + } + + @Test + fun testPowFull() { + val x = Variable(2) + val y = Variable(3) + val z = deriv { pow(x, y) } + assertApprox(8.0, z.value) // z = x ^ y = 8 + assertApprox(12.0, z.deriv(x)) // dz/dx = y * x ^ (y - 1) = 12 + assertApprox(8.0 * kotlin.math.ln(2.0), z.deriv(y)) // dz/dy = x ^ y * ln(x) + } + + @Test + fun testFromPaper() { + val x = Variable(3) + val y = deriv { 2 * x + x * x * x } + assertEquals(33.0, y.value) // y = 2 * x + x * x * x = 33 + assertEquals(29.0, y.deriv(x)) // dy/dx = 2 + 3 * x * x = 29 + } + + @Test + fun testInnerVariable() { + val x = Variable(1) + val y = deriv { + Variable(1) * x + } + assertEquals(1.0, y.value) // y = x ^ n = 1 + assertEquals(1.0, y.deriv(x)) // dy/dx = n * x ^ (n - 1) = n - 1 + } + + @Test + fun testLongChain() { + val n = 10_000 + val x = Variable(1) + val y = deriv { + var res = Variable(1) + for (i in 1..n) res *= x + res + } + assertEquals(1.0, y.value) // y = x ^ n = 1 + assertEquals(n.toDouble(), y.deriv(x)) // dy/dx = n * x ^ (n - 1) = n - 1 + } + + @Test + fun testExample() { + val x = Variable(2) + val y = deriv { sqr(x) + 5 * x + 3 } + assertEquals(17.0, y.value) // the value of result (y) + assertEquals(9.0, y.deriv(x)) // dy/dx + } + + @Test + fun testSqrt() { + val x = Variable(16) + val y = deriv { sqrt(x) } + assertEquals(4.0, y.value) // y = x ^ 1/2 = 4 + assertEquals(1.0 / 8, y.deriv(x)) // dy/dx = 1/2 / x ^ 1/4 = 1/8 + } + + @Test + fun testSin() { + val x = Variable(PI / 6) + val y = deriv { sin(x) } + assertApprox(0.5, y.value) // y = sin(PI/6) = 0.5 + assertApprox(kotlin.math.sqrt(3.0) / 2, y.deriv(x)) // dy/dx = cos(PI/6) = sqrt(3)/2 + } + + @Test + fun testCos() { + val x = Variable(PI / 6) + val y = deriv { cos(x) } + assertApprox(kotlin.math.sqrt(3.0) / 2, y.value) // y = cos(PI/6) = sqrt(3)/2 + assertApprox(-0.5, y.deriv(x)) // dy/dx = -sin(PI/6) = -0.5 + } + + @Test + fun testDivGrad() { + val x = Variable(1.0) + val y = Variable(2.0) + val res = deriv { x * x + y * y } + assertEquals(6.0, res.div()) + assertTrue(res.grad(x, y).contentEquals(doubleArrayOf(2.0, 4.0).asBuffer())) + } + + private fun assertApprox(a: Double, b: Double) { + if ((a - b) > 1e-10) assertEquals(a, b) + } +} diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/misc/CumulativeKtTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/CumulativeKtTest.kt similarity index 90% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/misc/CumulativeKtTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/CumulativeKtTest.kt index 1e6d2fd5d..82ea5318f 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/misc/CumulativeKtTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/CumulativeKtTest.kt @@ -1,4 +1,4 @@ -package kscience.kmath.misc +package scientifik.kmath.misc import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntAlgebraTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntAlgebraTest.kt similarity index 94% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntAlgebraTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntAlgebraTest.kt index 78611e5d2..d140f1017 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntAlgebraTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntAlgebraTest.kt @@ -1,6 +1,6 @@ -package kscience.kmath.operations +package scientifik.kmath.operations -import kscience.kmath.operations.internal.RingVerifier +import scientifik.kmath.operations.internal.RingVerifier import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntConstructorTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConstructorTest.kt similarity index 93% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntConstructorTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConstructorTest.kt index ba2582bbf..5e3f6d1b0 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntConstructorTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConstructorTest.kt @@ -1,4 +1,4 @@ -package kscience.kmath.operations +package scientifik.kmath.operations import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntConversionsTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConversionsTest.kt similarity index 96% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntConversionsTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConversionsTest.kt index 0b433c436..41df1968d 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntConversionsTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConversionsTest.kt @@ -1,4 +1,4 @@ -package kscience.kmath.operations +package scientifik.kmath.operations import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntOperationsTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntOperationsTest.kt similarity index 99% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntOperationsTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntOperationsTest.kt index a3ed85c7b..b7f4cf43b 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/BigIntOperationsTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntOperationsTest.kt @@ -1,4 +1,4 @@ -package kscience.kmath.operations +package scientifik.kmath.operations import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/ComplexFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/ComplexFieldTest.kt similarity index 96% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/ComplexFieldTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/ComplexFieldTest.kt index c0b4853f4..2c480ebea 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/ComplexFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/ComplexFieldTest.kt @@ -1,6 +1,6 @@ -package kscience.kmath.operations +package scientifik.kmath.operations -import kscience.kmath.operations.internal.FieldVerifier +import scientifik.kmath.operations.internal.FieldVerifier import kotlin.math.PI import kotlin.math.abs import kotlin.test.Test diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/ComplexTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/ComplexTest.kt similarity index 85% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/ComplexTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/ComplexTest.kt index 456e41467..e8d698c70 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/ComplexTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/ComplexTest.kt @@ -1,8 +1,7 @@ -package kscience.kmath.operations +package scientifik.kmath.operations import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertTrue internal class ComplexTest { @Test @@ -14,7 +13,7 @@ internal class ComplexTest { @Test fun reciprocal() { - assertTrue { (Complex(0.5, -0.0) - 2.toComplex().reciprocal).r < 1e-10} + assertEquals(Complex(0.5, -0.0), 2.toComplex().reciprocal) } @Test diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/RealFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt similarity index 75% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/RealFieldTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt index 5705733cf..a168b4afd 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/RealFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt @@ -1,6 +1,6 @@ -package kscience.kmath.operations +package scientifik.kmath.operations -import kscience.kmath.operations.internal.FieldVerifier +import scientifik.kmath.operations.internal.FieldVerifier import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/AlgebraicVerifier.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/AlgebraicVerifier.kt similarity index 55% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/AlgebraicVerifier.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/AlgebraicVerifier.kt index 7334c13a3..cb097d46e 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/AlgebraicVerifier.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/AlgebraicVerifier.kt @@ -1,6 +1,6 @@ -package kscience.kmath.operations.internal +package scientifik.kmath.operations.internal -import kscience.kmath.operations.Algebra +import scientifik.kmath.operations.Algebra internal interface AlgebraicVerifier where A : Algebra { val algebra: A diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/FieldVerifier.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/FieldVerifier.kt similarity index 88% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/FieldVerifier.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/FieldVerifier.kt index 1ca09ab0c..973fd00b1 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/FieldVerifier.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/FieldVerifier.kt @@ -1,7 +1,7 @@ -package kscience.kmath.operations.internal +package scientifik.kmath.operations.internal -import kscience.kmath.operations.Field -import kscience.kmath.operations.invoke +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.invoke import kotlin.test.assertEquals import kotlin.test.assertNotEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/RingVerifier.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/RingVerifier.kt similarity index 92% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/RingVerifier.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/RingVerifier.kt index 863169b9b..047a213e9 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/RingVerifier.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/RingVerifier.kt @@ -1,7 +1,7 @@ -package kscience.kmath.operations.internal +package scientifik.kmath.operations.internal -import kscience.kmath.operations.Ring -import kscience.kmath.operations.invoke +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.invoke import kotlin.test.assertEquals internal open class RingVerifier(override val algebra: Ring, a: T, b: T, c: T, x: Number) : diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/SpaceVerifier.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/SpaceVerifier.kt similarity index 92% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/SpaceVerifier.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/SpaceVerifier.kt index 4dc855829..bc241c97d 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/operations/internal/SpaceVerifier.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/internal/SpaceVerifier.kt @@ -1,7 +1,7 @@ -package kscience.kmath.operations.internal +package scientifik.kmath.operations.internal -import kscience.kmath.operations.Space -import kscience.kmath.operations.invoke +import scientifik.kmath.operations.Space +import scientifik.kmath.operations.invoke import kotlin.test.assertEquals import kotlin.test.assertNotEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/ComplexBufferSpecTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt similarity index 68% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/structures/ComplexBufferSpecTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt index 4837236db..cbbe6f0f4 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/ComplexBufferSpecTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt @@ -1,7 +1,7 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.Complex -import kscience.kmath.operations.complex +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.complex import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt similarity index 87% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NDFieldTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt index 79b56ea4a..7abeefca6 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt @@ -1,4 +1,4 @@ -package kscience.kmath.structures +package scientifik.kmath.structures import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt similarity index 79% rename from kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NumberNDFieldTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt index f5e008ef3..d48aabfd0 100644 --- a/kmath-core/src/commonTest/kotlin/kscience/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt @@ -1,8 +1,7 @@ -package kscience.kmath.structures +package scientifik.kmath.structures -import kscience.kmath.operations.Norm -import kscience.kmath.operations.invoke -import kscience.kmath.structures.NDElement.Companion.real2D +import scientifik.kmath.operations.Norm +import scientifik.kmath.structures.NDElement.Companion.real2D import kotlin.math.abs import kotlin.math.pow import kotlin.test.Test @@ -57,12 +56,17 @@ class NumberNDFieldTest { } object L2Norm : Norm, Double> { - override fun norm(arg: NDStructure): Double = - kotlin.math.sqrt(arg.elements().sumByDouble { it.second.toDouble() }) + override fun norm(arg: NDStructure): Double { + return kotlin.math.sqrt(arg.elements().sumByDouble { it.second.toDouble() }) + } } @Test fun testInternalContext() { - (NDField.real(*array1.shape)) { with(L2Norm) { 1 + norm(array1) + exp(array2) } } + NDField.real(*array1.shape).run { + with(L2Norm) { + 1 + norm(array1) + exp(array2) + } + } } } diff --git a/kmath-core/src/jvmMain/kotlin/kscience/kmath/operations/BigNumbers.kt b/kmath-core/src/jvmMain/kotlin/kscience/kmath/operations/BigNumbers.kt deleted file mode 100644 index 2f0978237..000000000 --- a/kmath-core/src/jvmMain/kotlin/kscience/kmath/operations/BigNumbers.kt +++ /dev/null @@ -1,59 +0,0 @@ -package kscience.kmath.operations - -import java.math.BigDecimal -import java.math.BigInteger -import java.math.MathContext - -/** - * A field over [BigInteger]. - */ -public object JBigIntegerField : Field { - public override val zero: BigInteger - get() = BigInteger.ZERO - - public override val one: BigInteger - get() = BigInteger.ONE - - public override fun number(value: Number): BigInteger = BigInteger.valueOf(value.toLong()) - public override fun divide(a: BigInteger, b: BigInteger): BigInteger = a.div(b) - public override fun add(a: BigInteger, b: BigInteger): BigInteger = a.add(b) - public override operator fun BigInteger.minus(b: BigInteger): BigInteger = subtract(b) - public override fun multiply(a: BigInteger, k: Number): BigInteger = a.multiply(k.toInt().toBigInteger()) - public override fun multiply(a: BigInteger, b: BigInteger): BigInteger = a.multiply(b) - public override operator fun BigInteger.unaryMinus(): BigInteger = negate() -} - -/** - * An abstract field over [BigDecimal]. - * - * @property mathContext the [MathContext] to use. - */ -public abstract class JBigDecimalFieldBase internal constructor(public val mathContext: MathContext = MathContext.DECIMAL64) : - Field, - PowerOperations { - public override val zero: BigDecimal - get() = BigDecimal.ZERO - - public override val one: BigDecimal - get() = BigDecimal.ONE - - public override fun add(a: BigDecimal, b: BigDecimal): BigDecimal = a.add(b) - public override operator fun BigDecimal.minus(b: BigDecimal): BigDecimal = subtract(b) - public override fun number(value: Number): BigDecimal = BigDecimal.valueOf(value.toDouble()) - - public override fun multiply(a: BigDecimal, k: Number): BigDecimal = - a.multiply(k.toDouble().toBigDecimal(mathContext), mathContext) - - public override fun multiply(a: BigDecimal, b: BigDecimal): BigDecimal = a.multiply(b, mathContext) - public override fun divide(a: BigDecimal, b: BigDecimal): BigDecimal = a.divide(b, mathContext) - public override fun power(arg: BigDecimal, pow: Number): BigDecimal = arg.pow(pow.toInt(), mathContext) - public override fun sqrt(arg: BigDecimal): BigDecimal = arg.sqrt(mathContext) - public override operator fun BigDecimal.unaryMinus(): BigDecimal = negate(mathContext) -} - -/** - * A field over [BigDecimal]. - */ -public class JBigDecimalField(mathContext: MathContext = MathContext.DECIMAL64) : JBigDecimalFieldBase(mathContext) { - public companion object : JBigDecimalFieldBase() -} diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/operations/BigNumbers.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/operations/BigNumbers.kt new file mode 100644 index 000000000..15d4727a4 --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/operations/BigNumbers.kt @@ -0,0 +1,62 @@ +package scientifik.kmath.operations + +import java.math.BigDecimal +import java.math.BigInteger +import java.math.MathContext + +/** + * A field over [BigInteger]. + */ +object JBigIntegerField : Ring, RemainderDivisionOperations { + override val zero: BigInteger + get() = BigInteger.ZERO + + override val one: BigInteger + get() = BigInteger.ONE + + override fun number(value: Number): BigInteger = value.toLong().toBigInteger() + override fun add(a: BigInteger, b: BigInteger): BigInteger = a.add(b) + override fun BigInteger.minus(b: BigInteger): BigInteger = subtract(b) + override fun multiply(a: BigInteger, k: Number): BigInteger = a.multiply(k.toLong().toBigInteger()) + override fun multiply(a: BigInteger, b: BigInteger): BigInteger = a.multiply(b) + override fun BigInteger.unaryMinus(): BigInteger = negate() + override fun BigInteger.div(arg: BigInteger): BigInteger = divide(arg) + override fun BigInteger.div(k: Number): BigInteger = this / k.toLong().toBigInteger() + override fun BigInteger.rem(arg: BigInteger): BigInteger = remainder(arg) +} + +/** + * An abstract field over [BigDecimal]. + * + * @property mathContext the [MathContext] to use. + */ +abstract class JBigDecimalFieldBase internal constructor(val mathContext: MathContext = MathContext.DECIMAL64) : + Field, + PowerOperations { + override val zero: BigDecimal + get() = BigDecimal.ZERO + + override val one: BigDecimal + get() = BigDecimal.ONE + + override fun add(a: BigDecimal, b: BigDecimal): BigDecimal = a.add(b) + override fun BigDecimal.minus(b: BigDecimal): BigDecimal = subtract(b) + override fun number(value: Number): BigDecimal = value.toDouble().toBigDecimal(mathContext) + + override fun multiply(a: BigDecimal, k: Number): BigDecimal = + a.multiply(k.toDouble().toBigDecimal(mathContext), mathContext) + + override fun multiply(a: BigDecimal, b: BigDecimal): BigDecimal = a.multiply(b, mathContext) + override fun divide(a: BigDecimal, b: BigDecimal): BigDecimal = a.divide(b, mathContext) + override fun power(arg: BigDecimal, pow: Number): BigDecimal = arg.pow(pow.toInt(), mathContext) + override fun sqrt(arg: BigDecimal): BigDecimal = arg.sqrt(mathContext) + override fun BigDecimal.unaryMinus(): BigDecimal = negate(mathContext) + +} + +/** + * A field over [BigDecimal]. + */ +class JBigDecimalField(mathContext: MathContext = MathContext.DECIMAL64) : JBigDecimalFieldBase(mathContext) { + companion object : JBigDecimalFieldBase() +} diff --git a/kmath-coroutines/build.gradle.kts b/kmath-coroutines/build.gradle.kts index e108c2755..373d9b8ac 100644 --- a/kmath-coroutines/build.gradle.kts +++ b/kmath-coroutines/build.gradle.kts @@ -1,18 +1,23 @@ -plugins { id("ru.mipt.npm.mpp") } +plugins { + id("scientifik.mpp") + //id("scientifik.atomic") +} kotlin.sourceSets { - all { - with(languageSettings) { - useExperimentalAnnotation("kotlinx.coroutines.InternalCoroutinesApi") - useExperimentalAnnotation("kotlinx.coroutines.ExperimentalCoroutinesApi") - useExperimentalAnnotation("kotlinx.coroutines.FlowPreview") - } - } - commonMain { dependencies { api(project(":kmath-core")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Scientifik.coroutinesVersion}") + } + } + jvmMain { + dependencies { + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Scientifik.coroutinesVersion}") + } + } + jsMain { + dependencies { + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Scientifik.coroutinesVersion}") } } } diff --git a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/BlockingIntChain.kt b/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/BlockingIntChain.kt deleted file mode 100644 index 6088267a2..000000000 --- a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/BlockingIntChain.kt +++ /dev/null @@ -1,12 +0,0 @@ -package kscience.kmath.chains - -/** - * Performance optimized chain for integer values - */ -public abstract class BlockingIntChain : Chain { - public abstract fun nextInt(): Int - - override suspend fun next(): Int = nextInt() - - public fun nextBlock(size: Int): IntArray = IntArray(size) { nextInt() } -} diff --git a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/BlockingRealChain.kt b/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/BlockingRealChain.kt deleted file mode 100644 index 718b3a18b..000000000 --- a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/BlockingRealChain.kt +++ /dev/null @@ -1,12 +0,0 @@ -package kscience.kmath.chains - -/** - * Performance optimized chain for real values - */ -public abstract class BlockingRealChain : Chain { - public abstract fun nextDouble(): Double - - override suspend fun next(): Double = nextDouble() - - public fun nextBlock(size: Int): DoubleArray = DoubleArray(size) { nextDouble() } -} diff --git a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/flowExtra.kt b/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/flowExtra.kt deleted file mode 100644 index 6b14057fe..000000000 --- a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/flowExtra.kt +++ /dev/null @@ -1,26 +0,0 @@ -package kscience.kmath.chains - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.runningReduce -import kotlinx.coroutines.flow.scan -import kscience.kmath.operations.Space -import kscience.kmath.operations.SpaceOperations -import kscience.kmath.operations.invoke - -@ExperimentalCoroutinesApi -public fun Flow.cumulativeSum(space: SpaceOperations): Flow = - space { runningReduce { sum, element -> sum + element } } - -@ExperimentalCoroutinesApi -public fun Flow.mean(space: Space): Flow = space { - data class Accumulator(var sum: T, var num: Int) - - scan(Accumulator(zero, 0)) { sum, element -> - sum.apply { - this.sum += element - this.num += 1 - } - }.map { it.sum / it.num } -} diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/BlockingIntChain.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/BlockingIntChain.kt new file mode 100644 index 000000000..e9b499d71 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/BlockingIntChain.kt @@ -0,0 +1,12 @@ +package scientifik.kmath.chains + +/** + * Performance optimized chain for integer values + */ +abstract class BlockingIntChain : Chain { + abstract fun nextInt(): Int + + override suspend fun next(): Int = nextInt() + + fun nextBlock(size: Int): IntArray = IntArray(size) { nextInt() } +} diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/BlockingRealChain.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/BlockingRealChain.kt new file mode 100644 index 000000000..ab819d327 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/BlockingRealChain.kt @@ -0,0 +1,12 @@ +package scientifik.kmath.chains + +/** + * Performance optimized chain for real values + */ +abstract class BlockingRealChain : Chain { + abstract fun nextDouble(): Double + + override suspend fun next(): Double = nextDouble() + + fun nextBlock(size: Int): DoubleArray = DoubleArray(size) { nextDouble() } +} diff --git a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/Chain.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt similarity index 56% rename from kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/Chain.kt rename to kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt index 7ff7b7aae..6cc9770af 100644 --- a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/chains/Chain.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package kscience.kmath.chains +package scientifik.kmath.chains +import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -26,44 +26,53 @@ import kotlinx.coroutines.sync.withLock * A not-necessary-Markov chain of some type * @param R - the chain element type */ -public interface Chain : Flow { +interface Chain : Flow { /** * Generate next value, changing state if needed */ - public suspend fun next(): R + suspend fun next(): R /** * Create a copy of current chain state. Consuming resulting chain does not affect initial chain */ - public fun fork(): Chain + fun fork(): Chain - override suspend fun collect(collector: FlowCollector): Unit = - flow { while (true) emit(next()) }.collect(collector) + @OptIn(InternalCoroutinesApi::class) + override suspend fun collect(collector: FlowCollector) { + kotlinx.coroutines.flow.flow { + while (true) { + emit(next()) + } + }.collect(collector) + } - public companion object + companion object } -public fun Iterator.asChain(): Chain = SimpleChain { next() } -public fun Sequence.asChain(): Chain = iterator().asChain() + +fun Iterator.asChain(): Chain = SimpleChain { next() } +fun Sequence.asChain(): Chain = iterator().asChain() /** - * A simple chain of independent tokens. [fork] returns the same chain. + * A simple chain of independent tokens */ -public class SimpleChain(private val gen: suspend () -> R) : Chain { - public override suspend fun next(): R = gen() - public override fun fork(): Chain = this +class SimpleChain(private val gen: suspend () -> R) : Chain { + override suspend fun next(): R = gen() + override fun fork(): Chain = this } /** * A stateless Markov chain */ -public class MarkovChain(private val seed: suspend () -> R, private val gen: suspend (R) -> R) : Chain { - private val mutex: Mutex = Mutex() +class MarkovChain(private val seed: suspend () -> R, private val gen: suspend (R) -> R) : Chain { + + private val mutex = Mutex() + private var value: R? = null - public fun value(): R? = value + fun value(): R? = value - public override suspend fun next(): R { + override suspend fun next(): R { mutex.withLock { val newValue = gen(value ?: seed()) value = newValue @@ -71,7 +80,9 @@ public class MarkovChain(private val seed: suspend () -> R, private } } - public override fun fork(): Chain = MarkovChain(seed = { value ?: seed() }, gen = gen) + override fun fork(): Chain { + return MarkovChain(seed = { value ?: seed() }, gen = gen) + } } /** @@ -79,18 +90,19 @@ public class MarkovChain(private val seed: suspend () -> R, private * @param S - the state of the chain * @param forkState - the function to copy current state without modifying it */ -public class StatefulChain( +class StatefulChain( private val state: S, private val seed: S.() -> R, private val forkState: ((S) -> S), private val gen: suspend S.(R) -> R ) : Chain { private val mutex: Mutex = Mutex() + private var value: R? = null - public fun value(): R? = value + fun value(): R? = value - public override suspend fun next(): R { + override suspend fun next(): R { mutex.withLock { val newValue = state.gen(value ?: state.seed()) value = newValue @@ -98,22 +110,25 @@ public class StatefulChain( } } - public override fun fork(): Chain = StatefulChain(forkState(state), seed, forkState, gen) + override fun fork(): Chain = StatefulChain(forkState(state), seed, forkState, gen) } /** * A chain that repeats the same value */ -public class ConstantChain(public val value: T) : Chain { - public override suspend fun next(): T = value - public override fun fork(): Chain = this +class ConstantChain(val value: T) : Chain { + override suspend fun next(): T = value + + override fun fork(): Chain { + return this + } } /** * Map the chain result using suspended transformation. Initial chain result can no longer be safely consumed * since mapped chain consumes tokens. Accepts regular transformation function */ -public fun Chain.map(func: suspend (T) -> R): Chain = object : Chain { +fun Chain.map(func: suspend (T) -> R): Chain = object : Chain { override suspend fun next(): R = func(this@map.next()) override fun fork(): Chain = this@map.fork().map(func) } @@ -121,13 +136,12 @@ public fun Chain.map(func: suspend (T) -> R): Chain = object : Chai /** * [block] must be a pure function or at least not use external random variables, otherwise fork could be broken */ -public fun Chain.filter(block: (T) -> Boolean): Chain = object : Chain { +fun Chain.filter(block: (T) -> Boolean): Chain = object : Chain { override suspend fun next(): T { var next: T - - do next = this@filter.next() - while (!block(next)) - + do { + next = this@filter.next() + } while (!block(next)) return next } @@ -137,26 +151,23 @@ public fun Chain.filter(block: (T) -> Boolean): Chain = object : Chain /** * Map the whole chain */ -public fun Chain.collect(mapper: suspend (Chain) -> R): Chain = object : Chain { +fun Chain.collect(mapper: suspend (Chain) -> R): Chain = object : Chain { override suspend fun next(): R = mapper(this@collect) override fun fork(): Chain = this@collect.fork().collect(mapper) } -public fun Chain.collectWithState( - state: S, - stateFork: (S) -> S, - mapper: suspend S.(Chain) -> R -): Chain = object : Chain { - override suspend fun next(): R = state.mapper(this@collectWithState) - - override fun fork(): Chain = - this@collectWithState.fork().collectWithState(stateFork(state), stateFork, mapper) -} +fun Chain.collectWithState(state: S, stateFork: (S) -> S, mapper: suspend S.(Chain) -> R): Chain = + object : Chain { + override suspend fun next(): R = state.mapper(this@collectWithState) + override fun fork(): Chain = + this@collectWithState.fork().collectWithState(stateFork(state), stateFork, mapper) + } /** * Zip two chains together using given transformation */ -public fun Chain.zip(other: Chain, block: suspend (T, U) -> R): Chain = object : Chain { +fun Chain.zip(other: Chain, block: suspend (T, U) -> R): Chain = object : Chain { override suspend fun next(): R = block(this@zip.next(), other.next()) + override fun fork(): Chain = this@zip.fork().zip(other.fork(), block) } diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/flowExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/flowExtra.kt new file mode 100644 index 000000000..e8537304c --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/flowExtra.kt @@ -0,0 +1,27 @@ +package scientifik.kmath.chains + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.flow.scanReduce +import scientifik.kmath.operations.Space +import scientifik.kmath.operations.SpaceOperations + + +@ExperimentalCoroutinesApi +fun Flow.cumulativeSum(space: SpaceOperations): Flow = with(space) { + scanReduce { sum: T, element: T -> sum + element } +} + +@ExperimentalCoroutinesApi +fun Flow.mean(space: Space): Flow = with(space) { + class Accumulator(var sum: T, var num: Int) + + scan(Accumulator(zero, 0)) { sum, element -> + sum.apply { + this.sum += element + this.num += 1 + } + }.map { it.sum / it.num } +} diff --git a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/coroutines/coroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/coroutines/coroutinesExtra.kt similarity index 57% rename from kmath-coroutines/src/commonMain/kotlin/kscience/kmath/coroutines/coroutinesExtra.kt rename to kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/coroutines/coroutinesExtra.kt index 7dcdc0d62..7e00b30a1 100644 --- a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/coroutines/coroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/coroutines/coroutinesExtra.kt @@ -1,10 +1,10 @@ -package kscience.kmath.coroutines +package scientifik.kmath.coroutines import kotlinx.coroutines.* import kotlinx.coroutines.channels.produce import kotlinx.coroutines.flow.* -public val Dispatchers.Math: CoroutineDispatcher +val Dispatchers.Math: CoroutineDispatcher get() = Default /** @@ -14,26 +14,36 @@ internal class LazyDeferred(val dispatcher: CoroutineDispatcher, val block: s private var deferred: Deferred? = null internal fun start(scope: CoroutineScope) { - if (deferred == null) deferred = scope.async(dispatcher, block = block) + if (deferred == null) { + deferred = scope.async(dispatcher, block = block) + } } suspend fun await(): T = deferred?.await() ?: error("Coroutine not started") } -public class AsyncFlow internal constructor(internal val deferredFlow: Flow>) : Flow { - override suspend fun collect(collector: FlowCollector): Unit = - deferredFlow.collect { collector.emit((it.await())) } +class AsyncFlow internal constructor(internal val deferredFlow: Flow>) : Flow { + @InternalCoroutinesApi + override suspend fun collect(collector: FlowCollector) { + deferredFlow.collect { + collector.emit((it.await())) + } + } } -public fun Flow.async( +@FlowPreview +fun Flow.async( dispatcher: CoroutineDispatcher = Dispatchers.Default, block: suspend CoroutineScope.(T) -> R ): AsyncFlow { - val flow = map { LazyDeferred(dispatcher) { block(it) } } + val flow = map { + LazyDeferred(dispatcher) { block(it) } + } return AsyncFlow(flow) } -public fun AsyncFlow.map(action: (T) -> R): AsyncFlow = +@FlowPreview +fun AsyncFlow.map(action: (T) -> R): AsyncFlow = AsyncFlow(deferredFlow.map { input -> //TODO add function composition LazyDeferred(input.dispatcher) { @@ -42,9 +52,10 @@ public fun AsyncFlow.map(action: (T) -> R): AsyncFlow = } }) -public suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCollector) { +@ExperimentalCoroutinesApi +@FlowPreview +suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCollector) { require(concurrency >= 1) { "Buffer size should be more than 1, but was $concurrency" } - coroutineScope { //Starting up to N deferred coroutines ahead of time val channel = produce(capacity = concurrency - 1) { @@ -70,14 +81,21 @@ public suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCol } } -public suspend inline fun AsyncFlow.collect( - concurrency: Int, - crossinline action: suspend (value: T) -> Unit -): Unit = collect(concurrency, object : FlowCollector { - override suspend fun emit(value: T): Unit = action(value) -}) +@ExperimentalCoroutinesApi +@FlowPreview +suspend fun AsyncFlow.collect(concurrency: Int, action: suspend (value: T) -> Unit) { + collect(concurrency, object : FlowCollector { + override suspend fun emit(value: T): Unit = action(value) + }) +} -public inline fun Flow.mapParallel( +@ExperimentalCoroutinesApi +@FlowPreview +fun Flow.mapParallel( dispatcher: CoroutineDispatcher = Dispatchers.Default, - crossinline transform: suspend (T) -> R -): Flow = flatMapMerge { value -> flow { emit(transform(value)) } }.flowOn(dispatcher) + transform: suspend (T) -> R +): Flow { + return flatMapMerge { value -> + flow { emit(transform(value)) } + }.flowOn(dispatcher) +} diff --git a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/streaming/BufferFlow.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt similarity index 62% rename from kmath-coroutines/src/commonMain/kotlin/kscience/kmath/streaming/BufferFlow.kt rename to kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt index 328a7807c..9b7e82da5 100644 --- a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/streaming/BufferFlow.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt @@ -1,28 +1,28 @@ -package kscience.kmath.streaming +package scientifik.kmath.streaming import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* -import kscience.kmath.chains.BlockingRealChain -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.BufferFactory -import kscience.kmath.structures.RealBuffer -import kscience.kmath.structures.asBuffer +import scientifik.kmath.chains.BlockingRealChain +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory +import scientifik.kmath.structures.RealBuffer +import scientifik.kmath.structures.asBuffer /** * Create a [Flow] from buffer */ -public fun Buffer.asFlow(): Flow = iterator().asFlow() +fun Buffer.asFlow(): Flow = iterator().asFlow() /** * Flat map a [Flow] of [Buffer] into continuous [Flow] of elements */ @FlowPreview -public fun Flow>.spread(): Flow = flatMapConcat { it.asFlow() } +fun Flow>.spread(): Flow = flatMapConcat { it.asFlow() } /** * Collect incoming flow into fixed size chunks */ -public fun Flow.chunked(bufferSize: Int, bufferFactory: BufferFactory): Flow> = flow { +fun Flow.chunked(bufferSize: Int, bufferFactory: BufferFactory): Flow> = flow { require(bufferSize > 0) { "Resulting chunk size must be more than zero" } val list = ArrayList(bufferSize) var counter = 0 @@ -30,7 +30,6 @@ public fun Flow.chunked(bufferSize: Int, bufferFactory: BufferFactory) this@chunked.collect { element -> list.add(element) counter++ - if (counter == bufferSize) { val buffer = bufferFactory(bufferSize) { list[it] } emit(buffer) @@ -38,19 +37,22 @@ public fun Flow.chunked(bufferSize: Int, bufferFactory: BufferFactory) counter = 0 } } - - if (counter > 0) emit(bufferFactory(counter) { list[it] }) + if (counter > 0) { + emit(bufferFactory(counter) { list[it] }) + } } /** * Specialized flow chunker for real buffer */ -public fun Flow.chunked(bufferSize: Int): Flow = flow { +fun Flow.chunked(bufferSize: Int): Flow = flow { require(bufferSize > 0) { "Resulting chunk size must be more than zero" } if (this@chunked is BlockingRealChain) { - // performance optimization for blocking primitive chain - while (true) emit(nextBlock(bufferSize).asBuffer()) + //performance optimization for blocking primitive chain + while (true) { + emit(nextBlock(bufferSize).asBuffer()) + } } else { val array = DoubleArray(bufferSize) var counter = 0 @@ -58,15 +60,15 @@ public fun Flow.chunked(bufferSize: Int): Flow = flow { this@chunked.collect { element -> array[counter] = element counter++ - if (counter == bufferSize) { val buffer = RealBuffer(array) emit(buffer) counter = 0 } } - - if (counter > 0) emit(RealBuffer(counter) { array[it] }) + if (counter > 0) { + emit(RealBuffer(counter) { array[it] }) + } } } @@ -74,10 +76,9 @@ public fun Flow.chunked(bufferSize: Int): Flow = flow { * Map a flow to a moving window buffer. The window step is one. * In order to get different steps, one could use skip operation. */ -public fun Flow.windowed(window: Int): Flow> = flow { +fun Flow.windowed(window: Int): Flow> = flow { require(window > 1) { "Window size must be more than one" } val ringBuffer = RingBuffer.boxing(window) - this@windowed.collect { element -> ringBuffer.push(element) emit(ringBuffer.snapshot()) diff --git a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/streaming/RingBuffer.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt similarity index 63% rename from kmath-coroutines/src/commonMain/kotlin/kscience/kmath/streaming/RingBuffer.kt rename to kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt index 385bbaae2..245d003b3 100644 --- a/kmath-coroutines/src/commonMain/kotlin/kscience/kmath/streaming/RingBuffer.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt @@ -1,43 +1,45 @@ -package kscience.kmath.streaming +package scientifik.kmath.streaming import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.MutableBuffer -import kscience.kmath.structures.VirtualBuffer +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.MutableBuffer +import scientifik.kmath.structures.VirtualBuffer /** * Thread-safe ring buffer */ @Suppress("UNCHECKED_CAST") -public class RingBuffer( +class RingBuffer( private val buffer: MutableBuffer, private var startIndex: Int = 0, size: Int = 0 ) : Buffer { private val mutex: Mutex = Mutex() - public override var size: Int = size + override var size: Int = size private set - public override operator fun get(index: Int): T { + override fun get(index: Int): T { require(index >= 0) { "Index must be positive" } require(index < size) { "Index $index is out of circular buffer size $size" } return buffer[startIndex.forward(index)] as T } - public fun isFull(): Boolean = size == buffer.size + fun isFull(): Boolean = size == buffer.size /** * Iterator could provide wrong results if buffer is changed in initialization (iteration is safe) */ - public override operator fun iterator(): Iterator = object : AbstractIterator() { + override fun iterator(): Iterator = object : AbstractIterator() { private var count = size private var index = startIndex val copy = buffer.copy() override fun computeNext() { - if (count == 0) done() else { + if (count == 0) { + done() + } else { setNext(copy[index] as T) index = index.forward(1) count-- @@ -48,17 +50,23 @@ public class RingBuffer( /** * A safe snapshot operation */ - public suspend fun snapshot(): Buffer { + suspend fun snapshot(): Buffer { mutex.withLock { val copy = buffer.copy() - return VirtualBuffer(size) { i -> copy[startIndex.forward(i)] as T } + return VirtualBuffer(size) { i -> + copy[startIndex.forward(i)] as T + } } } - public suspend fun push(element: T) { + suspend fun push(element: T) { mutex.withLock { buffer[startIndex.forward(size)] = element - if (isFull()) startIndex++ else size++ + if (isFull()) { + startIndex++ + } else { + size++ + } } } @@ -66,8 +74,8 @@ public class RingBuffer( @Suppress("NOTHING_TO_INLINE") private inline fun Int.forward(n: Int): Int = (this + n) % (buffer.size) - public companion object { - public inline fun build(size: Int, empty: T): RingBuffer { + companion object { + inline fun build(size: Int, empty: T): RingBuffer { val buffer = MutableBuffer.auto(size) { empty } as MutableBuffer return RingBuffer(buffer) } @@ -75,7 +83,7 @@ public class RingBuffer( /** * Slow yet universal buffer */ - public fun boxing(size: Int): RingBuffer { + fun boxing(size: Int): RingBuffer { val buffer: MutableBuffer = MutableBuffer.boxing(size) { null } return RingBuffer(buffer) } diff --git a/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/chains/ChainExt.kt b/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/chains/ChainExt.kt deleted file mode 100644 index 3dfeddbac..000000000 --- a/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/chains/ChainExt.kt +++ /dev/null @@ -1,16 +0,0 @@ -package kscience.kmath.chains - -import kotlinx.coroutines.runBlocking - -/** - * Represent a chain as regular iterator (uses blocking calls) - */ -public operator fun Chain.iterator(): Iterator = object : Iterator { - override fun hasNext(): Boolean = true - override fun next(): R = runBlocking { next() } -} - -/** - * Represent a chain as a sequence - */ -public fun Chain.asSequence(): Sequence = Sequence { this@asSequence.iterator() } diff --git a/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/structures/LazyNDStructure.kt b/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/structures/LazyNDStructure.kt deleted file mode 100644 index bb0d19c23..000000000 --- a/kmath-coroutines/src/jvmMain/kotlin/kscience/kmath/structures/LazyNDStructure.kt +++ /dev/null @@ -1,56 +0,0 @@ -package kscience.kmath.structures - -import kotlinx.coroutines.* -import kscience.kmath.coroutines.Math - -public class LazyNDStructure( - public val scope: CoroutineScope, - public override val shape: IntArray, - public val function: suspend (IntArray) -> T -) : NDStructure { - private val cache: MutableMap> = hashMapOf() - - public fun deferred(index: IntArray): Deferred = cache.getOrPut(index) { - scope.async(context = Dispatchers.Math) { function(index) } - } - - public suspend fun await(index: IntArray): T = deferred(index).await() - public override operator fun get(index: IntArray): T = runBlocking { deferred(index).await() } - - public override fun elements(): Sequence> { - val strides = DefaultStrides(shape) - val res = runBlocking { strides.indices().toList().map { index -> index to await(index) } } - return res.asSequence() - } - - public override fun equals(other: Any?): Boolean { - return NDStructure.equals(this, other as? NDStructure<*> ?: return false) - } - - public override fun hashCode(): Int { - var result = scope.hashCode() - result = 31 * result + shape.contentHashCode() - result = 31 * result + function.hashCode() - result = 31 * result + cache.hashCode() - return result - } -} - -public fun NDStructure.deferred(index: IntArray): Deferred = - if (this is LazyNDStructure) deferred(index) else CompletableDeferred(get(index)) - -public suspend fun NDStructure.await(index: IntArray): T = - if (this is LazyNDStructure) await(index) else get(index) - -/** - * PENDING would benefit from KEEP-176 - */ -public inline fun NDStructure.mapAsyncIndexed( - scope: CoroutineScope, - crossinline function: suspend (T, index: IntArray) -> R -): LazyNDStructure = LazyNDStructure(scope, shape) { index -> function(get(index), index) } - -public inline fun NDStructure.mapAsync( - scope: CoroutineScope, - crossinline function: suspend (T) -> R -): LazyNDStructure = LazyNDStructure(scope, shape) { index -> function(get(index)) } diff --git a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt new file mode 100644 index 000000000..0a3c67e00 --- /dev/null +++ b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt @@ -0,0 +1,20 @@ +package scientifik.kmath.chains + +import kotlinx.coroutines.runBlocking +import kotlin.sequences.Sequence + +/** + * Represent a chain as regular iterator (uses blocking calls) + */ +operator fun Chain.iterator(): Iterator = object : Iterator { + override fun hasNext(): Boolean = true + + override fun next(): R = runBlocking { next() } +} + +/** + * Represent a chain as a sequence + */ +fun Chain.asSequence(): Sequence = object : Sequence { + override fun iterator(): Iterator = this@asSequence.iterator() +} \ No newline at end of file diff --git a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt new file mode 100644 index 000000000..8d5145976 --- /dev/null +++ b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt @@ -0,0 +1,61 @@ +package scientifik.kmath.structures + +import kotlinx.coroutines.* +import scientifik.kmath.coroutines.Math + +class LazyNDStructure( + val scope: CoroutineScope, + override val shape: IntArray, + val function: suspend (IntArray) -> T +) : NDStructure { + private val cache: MutableMap> = hashMapOf() + + fun deferred(index: IntArray): Deferred = cache.getOrPut(index) { + scope.async(context = Dispatchers.Math) { + function(index) + } + } + + suspend fun await(index: IntArray): T = deferred(index).await() + + override fun get(index: IntArray): T = runBlocking { + deferred(index).await() + } + + override fun elements(): Sequence> { + val strides = DefaultStrides(shape) + val res = runBlocking { + strides.indices().toList().map { index -> index to await(index) } + } + return res.asSequence() + } + + override fun equals(other: Any?): Boolean { + return NDStructure.equals(this, other as? NDStructure<*> ?: return false) + } + + override fun hashCode(): Int { + var result = scope.hashCode() + result = 31 * result + shape.contentHashCode() + result = 31 * result + function.hashCode() + result = 31 * result + cache.hashCode() + return result + } +} + +fun NDStructure.deferred(index: IntArray): Deferred = + if (this is LazyNDStructure) this.deferred(index) else CompletableDeferred(get(index)) + +suspend fun NDStructure.await(index: IntArray): T = + if (this is LazyNDStructure) this.await(index) else get(index) + +/** + * PENDING would benefit from KEEP-176 + */ +fun NDStructure.mapAsyncIndexed( + scope: CoroutineScope, + function: suspend (T, index: IntArray) -> R +): LazyNDStructure = LazyNDStructure(scope, shape) { index -> function(get(index), index) } + +fun NDStructure.mapAsync(scope: CoroutineScope, function: suspend (T) -> R): LazyNDStructure = + LazyNDStructure(scope, shape) { index -> function(get(index)) } \ No newline at end of file diff --git a/kmath-coroutines/src/jvmTest/kotlin/kscience/kmath/streaming/BufferFlowTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt similarity index 86% rename from kmath-coroutines/src/jvmTest/kotlin/kscience/kmath/streaming/BufferFlowTest.kt rename to kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt index a9bf38c12..427349072 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/kscience/kmath/streaming/BufferFlowTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt @@ -1,12 +1,12 @@ -package kscience.kmath.streaming +package scientifik.kmath.streaming import kotlinx.coroutines.* import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.collect -import kscience.kmath.coroutines.async -import kscience.kmath.coroutines.collect -import kscience.kmath.coroutines.mapParallel import org.junit.jupiter.api.Timeout +import scientifik.kmath.coroutines.async +import scientifik.kmath.coroutines.collect +import scientifik.kmath.coroutines.mapParallel import java.util.concurrent.Executors import kotlin.test.Test @@ -14,7 +14,7 @@ import kotlin.test.Test @ExperimentalCoroutinesApi @InternalCoroutinesApi @FlowPreview -internal class BufferFlowTest { +class BufferFlowTest { val dispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() @Test diff --git a/kmath-coroutines/src/jvmTest/kotlin/kscience/kmath/streaming/RingBufferTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt similarity index 88% rename from kmath-coroutines/src/jvmTest/kotlin/kscience/kmath/streaming/RingBufferTest.kt rename to kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt index 5bb0c1d40..c84ef89ef 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/kscience/kmath/streaming/RingBufferTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt @@ -1,12 +1,12 @@ -package kscience.kmath.streaming +package scientifik.kmath.streaming import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking -import kscience.kmath.structures.asSequence +import scientifik.kmath.structures.asSequence import kotlin.test.Test import kotlin.test.assertEquals -internal class RingBufferTest { +class RingBufferTest { @Test fun push() { val buffer = RingBuffer.build(20, Double.NaN) diff --git a/kmath-dimensions/build.gradle.kts b/kmath-dimensions/build.gradle.kts index 9bf89fc43..dda6cd2f0 100644 --- a/kmath-dimensions/build.gradle.kts +++ b/kmath-dimensions/build.gradle.kts @@ -1,9 +1,8 @@ plugins { - id("ru.mipt.npm.mpp") - id("ru.mipt.npm.native") + id("scientifik.mpp") } -description = "A proof of concept module for adding type-safe dimensions to structures" +description = "A proof of concept module for adding typ-safe dimensions to structures" kotlin.sourceSets { commonMain { @@ -12,13 +11,9 @@ kotlin.sourceSets { } } - jvmMain { - dependencies { + jvmMain{ + dependencies{ api(kotlin("reflect")) } } -} - -readme{ - maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE -} +} \ No newline at end of file diff --git a/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Dimensions.kt b/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Dimensions.kt deleted file mode 100644 index 9450f9174..000000000 --- a/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Dimensions.kt +++ /dev/null @@ -1,49 +0,0 @@ -package kscience.kmath.dimensions - -import kotlin.reflect.KClass - -/** - * Represents a quantity of dimensions in certain structure. - * - * @property dim The number of dimensions. - */ -public interface Dimension { - public val dim: UInt - - public companion object -} - -public fun KClass.dim(): UInt = Dimension.resolve(this).dim - -public expect fun Dimension.Companion.resolve(type: KClass): D - -/** - * Finds or creates [Dimension] with [Dimension.dim] equal to [dim]. - */ -public expect fun Dimension.Companion.of(dim: UInt): Dimension - -/** - * Finds [Dimension.dim] of given type [D]. - */ -public inline fun Dimension.Companion.dim(): UInt = D::class.dim() - -/** - * Type representing 1 dimension. - */ -public object D1 : Dimension { - override val dim: UInt get() = 1U -} - -/** - * Type representing 2 dimensions. - */ -public object D2 : Dimension { - override val dim: UInt get() = 2U -} - -/** - * Type representing 3 dimensions. - */ -public object D3 : Dimension { - override val dim: UInt get() = 3U -} diff --git a/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Wrappers.kt b/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Wrappers.kt deleted file mode 100644 index 68a5dc262..000000000 --- a/kmath-dimensions/src/commonMain/kotlin/kscience/kmath/dimensions/Wrappers.kt +++ /dev/null @@ -1,156 +0,0 @@ -package kscience.kmath.dimensions - -import kscience.kmath.linear.GenericMatrixContext -import kscience.kmath.linear.MatrixContext -import kscience.kmath.linear.Point -import kscience.kmath.linear.transpose -import kscience.kmath.operations.RealField -import kscience.kmath.operations.Ring -import kscience.kmath.operations.invoke -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.Structure2D - -/** - * A matrix with compile-time controlled dimension - */ -public interface DMatrix : Structure2D { - public companion object { - /** - * Coerces a regular matrix to a matrix with type-safe dimensions and throws a error if coercion failed - */ - public inline fun coerce(structure: Structure2D): DMatrix { - require(structure.rowNum == Dimension.dim().toInt()) { - "Row number mismatch: expected ${Dimension.dim()} but found ${structure.rowNum}" - } - - require(structure.colNum == Dimension.dim().toInt()) { - "Column number mismatch: expected ${Dimension.dim()} but found ${structure.colNum}" - } - - return DMatrixWrapper(structure) - } - - /** - * The same as [DMatrix.coerce] but without dimension checks. Use with caution - */ - public fun coerceUnsafe(structure: Structure2D): DMatrix = - DMatrixWrapper(structure) - } -} - -/** - * An inline wrapper for a Matrix - */ -public inline class DMatrixWrapper( - private val structure: Structure2D -) : DMatrix { - override val shape: IntArray get() = structure.shape - override operator fun get(i: Int, j: Int): T = structure[i, j] -} - -/** - * Dimension-safe point - */ -public interface DPoint : Point { - public companion object { - public inline fun coerce(point: Point): DPoint { - require(point.size == Dimension.dim().toInt()) { - "Vector dimension mismatch: expected ${Dimension.dim()}, but found ${point.size}" - } - - return DPointWrapper(point) - } - - public fun coerceUnsafe(point: Point): DPoint = DPointWrapper(point) - } -} - -/** - * Dimension-safe point wrapper - */ -public inline class DPointWrapper(public val point: Point) : - DPoint { - override val size: Int get() = point.size - - override operator fun get(index: Int): T = point[index] - - override operator fun iterator(): Iterator = point.iterator() -} - - -/** - * Basic operations on dimension-safe matrices. Operates on [Matrix] - */ -public inline class DMatrixContext>(public val context: GenericMatrixContext>) { - public inline fun Matrix.coerce(): DMatrix { - require(rowNum == Dimension.dim().toInt()) { - "Row number mismatch: expected ${Dimension.dim()} but found $rowNum" - } - - require(colNum == Dimension.dim().toInt()) { - "Column number mismatch: expected ${Dimension.dim()} but found $colNum" - } - - return DMatrix.coerceUnsafe(this) - } - - /** - * Produce a matrix with this context and given dimensions - */ - public inline fun produce(noinline initializer: (i: Int, j: Int) -> T): DMatrix { - val rows = Dimension.dim() - val cols = Dimension.dim() - return context.produce(rows.toInt(), cols.toInt(), initializer).coerce() - } - - public inline fun point(noinline initializer: (Int) -> T): DPoint { - val size = Dimension.dim() - - return DPoint.coerceUnsafe( - context.point( - size.toInt(), - initializer - ) - ) - } - - public inline infix fun DMatrix.dot( - other: DMatrix - ): DMatrix = context { this@dot dot other }.coerce() - - public inline infix fun DMatrix.dot(vector: DPoint): DPoint = - DPoint.coerceUnsafe(context { this@dot dot vector }) - - public inline operator fun DMatrix.times(value: T): DMatrix = - context { this@times.times(value) }.coerce() - - public inline operator fun T.times(m: DMatrix): DMatrix = - m * this - - public inline operator fun DMatrix.plus(other: DMatrix): DMatrix = - context { this@plus + other }.coerce() - - public inline operator fun DMatrix.minus(other: DMatrix): DMatrix = - context { this@minus + other }.coerce() - - public inline operator fun DMatrix.unaryMinus(): DMatrix = - context { this@unaryMinus.unaryMinus() }.coerce() - - public inline fun DMatrix.transpose(): DMatrix = - context { (this@transpose as Matrix).transpose() }.coerce() - - /** - * A square unit matrix - */ - public inline fun one(): DMatrix = produce { i, j -> - if (i == j) context.elementContext.one else context.elementContext.zero - } - - public inline fun zero(): DMatrix = produce { _, _ -> - context.elementContext.zero - } - - public companion object { - public val real: DMatrixContext = DMatrixContext(MatrixContext.real) - } -} diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt new file mode 100644 index 000000000..f40483cfd --- /dev/null +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt @@ -0,0 +1,35 @@ +package scientifik.kmath.dimensions + +import kotlin.reflect.KClass + +/** + * An abstract class which is not used in runtime. Designates a size of some structure. + * Could be replaced later by fully inline constructs + */ +interface Dimension { + + val dim: UInt + companion object { + + } +} + +fun KClass.dim(): UInt = Dimension.resolve(this).dim + +expect fun Dimension.Companion.resolve(type: KClass): D + +expect fun Dimension.Companion.of(dim: UInt): Dimension + +inline fun Dimension.Companion.dim(): UInt = D::class.dim() + +object D1 : Dimension { + override val dim: UInt get() = 1U +} + +object D2 : Dimension { + override val dim: UInt get() = 2U +} + +object D3 : Dimension { + override val dim: UInt get() = 3U +} diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt new file mode 100644 index 000000000..f447866c0 --- /dev/null +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt @@ -0,0 +1,161 @@ +package scientifik.kmath.dimensions + +import scientifik.kmath.linear.GenericMatrixContext +import scientifik.kmath.linear.MatrixContext +import scientifik.kmath.linear.Point +import scientifik.kmath.linear.transpose +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.Structure2D + +/** + * A matrix with compile-time controlled dimension + */ +interface DMatrix : Structure2D { + companion object { + /** + * Coerces a regular matrix to a matrix with type-safe dimensions and throws a error if coercion failed + */ + inline fun coerce(structure: Structure2D): DMatrix { + if (structure.rowNum != Dimension.dim().toInt()) { + error("Row number mismatch: expected ${Dimension.dim()} but found ${structure.rowNum}") + } + if (structure.colNum != Dimension.dim().toInt()) { + error("Column number mismatch: expected ${Dimension.dim()} but found ${structure.colNum}") + } + return DMatrixWrapper(structure) + } + + /** + * The same as [coerce] but without dimension checks. Use with caution + */ + fun coerceUnsafe(structure: Structure2D): DMatrix { + return DMatrixWrapper(structure) + } + } +} + +/** + * An inline wrapper for a Matrix + */ +inline class DMatrixWrapper( + val structure: Structure2D +) : DMatrix { + override val shape: IntArray get() = structure.shape + override fun get(i: Int, j: Int): T = structure[i, j] +} + +/** + * Dimension-safe point + */ +interface DPoint : Point { + companion object { + inline fun coerce(point: Point): DPoint { + if (point.size != Dimension.dim().toInt()) { + error("Vector dimension mismatch: expected ${Dimension.dim()}, but found ${point.size}") + } + return DPointWrapper(point) + } + + fun coerceUnsafe(point: Point): DPoint { + return DPointWrapper(point) + } + } +} + +/** + * Dimension-safe point wrapper + */ +inline class DPointWrapper(val point: Point) : + DPoint { + override val size: Int get() = point.size + + override fun get(index: Int): T = point[index] + + override fun iterator(): Iterator = point.iterator() +} + + +/** + * Basic operations on dimension-safe matrices. Operates on [Matrix] + */ +inline class DMatrixContext>(val context: GenericMatrixContext) { + + inline fun Matrix.coerce(): DMatrix { + if (rowNum != Dimension.dim().toInt()) { + error("Row number mismatch: expected ${Dimension.dim()} but found $rowNum") + } + if (colNum != Dimension.dim().toInt()) { + error("Column number mismatch: expected ${Dimension.dim()} but found $colNum") + } + return DMatrix.coerceUnsafe(this) + } + + /** + * Produce a matrix with this context and given dimensions + */ + inline fun produce(noinline initializer: (i: Int, j: Int) -> T): DMatrix { + val rows = Dimension.dim() + val cols = Dimension.dim() + return context.produce(rows.toInt(), cols.toInt(), initializer).coerce() + } + + inline fun point(noinline initializer: (Int) -> T): DPoint { + val size = Dimension.dim() + return DPoint.coerceUnsafe( + context.point( + size.toInt(), + initializer + ) + ) + } + + inline infix fun DMatrix.dot( + other: DMatrix + ): DMatrix { + return context.run { this@dot dot other }.coerce() + } + + inline infix fun DMatrix.dot(vector: DPoint): DPoint { + return DPoint.coerceUnsafe(context.run { this@dot dot vector }) + } + + inline operator fun DMatrix.times(value: T): DMatrix { + return context.run { this@times.times(value) }.coerce() + } + + inline operator fun T.times(m: DMatrix): DMatrix = + m * this + + + inline operator fun DMatrix.plus(other: DMatrix): DMatrix { + return context.run { this@plus + other }.coerce() + } + + inline operator fun DMatrix.minus(other: DMatrix): DMatrix { + return context.run { this@minus + other }.coerce() + } + + inline operator fun DMatrix.unaryMinus(): DMatrix { + return context.run { this@unaryMinus.unaryMinus() }.coerce() + } + + inline fun DMatrix.transpose(): DMatrix { + return context.run { (this@transpose as Matrix).transpose() }.coerce() + } + + /** + * A square unit matrix + */ + inline fun one(): DMatrix = produce { i, j -> + if (i == j) context.elementContext.one else context.elementContext.zero + } + + inline fun zero(): DMatrix = produce { _, _ -> + context.elementContext.zero + } + + companion object { + val real = DMatrixContext(MatrixContext.real) + } +} \ No newline at end of file diff --git a/kmath-dimensions/src/commonTest/kotlin/kscience/dimensions/DMatrixContextTest.kt b/kmath-dimensions/src/commonTest/kotlin/scientifik/dimensions/DMatrixContextTest.kt similarity index 64% rename from kmath-dimensions/src/commonTest/kotlin/kscience/dimensions/DMatrixContextTest.kt rename to kmath-dimensions/src/commonTest/kotlin/scientifik/dimensions/DMatrixContextTest.kt index f44b16753..74d20205c 100644 --- a/kmath-dimensions/src/commonTest/kotlin/kscience/dimensions/DMatrixContextTest.kt +++ b/kmath-dimensions/src/commonTest/kotlin/scientifik/dimensions/DMatrixContextTest.kt @@ -1,14 +1,15 @@ -package kscience.dimensions +package scientifik.dimensions -import kscience.kmath.dimensions.D2 -import kscience.kmath.dimensions.D3 -import kscience.kmath.dimensions.DMatrixContext +import scientifik.kmath.dimensions.D2 +import scientifik.kmath.dimensions.D3 +import scientifik.kmath.dimensions.DMatrixContext import kotlin.test.Test -internal class DMatrixContextTest { + +class DMatrixContextTest { @Test fun testDimensionSafeMatrix() { - val res = with(DMatrixContext.real) { + val res = DMatrixContext.real.run { val m = produce { i, j -> (i + j).toDouble() } //The dimension of `one()` is inferred from type @@ -18,7 +19,7 @@ internal class DMatrixContextTest { @Test fun testTypeCheck() { - val res = with(DMatrixContext.real) { + val res = DMatrixContext.real.run { val m1 = produce { i, j -> (i + j).toDouble() } val m2 = produce { i, j -> (i + j).toDouble() } @@ -26,4 +27,4 @@ internal class DMatrixContextTest { m1.transpose() + m2 } } -} +} \ No newline at end of file diff --git a/kmath-dimensions/src/jsMain/kotlin/kscience/kmath/dimensions/dimJs.kt b/kmath-dimensions/src/jsMain/kotlin/kscience/kmath/dimensions/dimJs.kt deleted file mode 100644 index 4230da156..000000000 --- a/kmath-dimensions/src/jsMain/kotlin/kscience/kmath/dimensions/dimJs.kt +++ /dev/null @@ -1,18 +0,0 @@ -package kscience.kmath.dimensions - -import kotlin.reflect.KClass - -private val dimensionMap: MutableMap = hashMapOf(1u to D1, 2u to D2, 3u to D3) - -@Suppress("UNCHECKED_CAST") -public actual fun Dimension.Companion.resolve(type: KClass): D = dimensionMap - .entries - .map(MutableMap.MutableEntry::value) - .find { it::class == type } as? D - ?: error("Can't resolve dimension $type") - -public actual fun Dimension.Companion.of(dim: UInt): Dimension = dimensionMap.getOrPut(dim) { - object : Dimension { - override val dim: UInt get() = dim - } -} diff --git a/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt b/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt new file mode 100644 index 000000000..bbd580629 --- /dev/null +++ b/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt @@ -0,0 +1,22 @@ +package scientifik.kmath.dimensions + +import kotlin.reflect.KClass + +private val dimensionMap = hashMapOf( + 1u to D1, + 2u to D2, + 3u to D3 +) + +@Suppress("UNCHECKED_CAST") +actual fun Dimension.Companion.resolve(type: KClass): D { + return dimensionMap.entries.find { it.value::class == type }?.value as? D ?: error("Can't resolve dimension $type") +} + +actual fun Dimension.Companion.of(dim: UInt): Dimension { + return dimensionMap.getOrPut(dim) { + object : Dimension { + override val dim: UInt get() = dim + } + } +} \ No newline at end of file diff --git a/kmath-dimensions/src/jvmMain/kotlin/kscience/kmath/dimensions/dimJvm.kt b/kmath-dimensions/src/jvmMain/kotlin/kscience/kmath/dimensions/dimJvm.kt deleted file mode 100644 index dec3979ef..000000000 --- a/kmath-dimensions/src/jvmMain/kotlin/kscience/kmath/dimensions/dimJvm.kt +++ /dev/null @@ -1,16 +0,0 @@ -package kscience.kmath.dimensions - -import kotlin.reflect.KClass - -public actual fun Dimension.Companion.resolve(type: KClass): D = - type.objectInstance ?: error("No object instance for dimension class") - -public actual fun Dimension.Companion.of(dim: UInt): Dimension = when (dim) { - 1u -> D1 - 2u -> D2 - 3u -> D3 - - else -> object : Dimension { - override val dim: UInt get() = dim - } -} \ No newline at end of file diff --git a/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt b/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt new file mode 100644 index 000000000..e8fe8f59b --- /dev/null +++ b/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt @@ -0,0 +1,18 @@ +package scientifik.kmath.dimensions + +import kotlin.reflect.KClass + +actual fun Dimension.Companion.resolve(type: KClass): D{ + return type.objectInstance ?: error("No object instance for dimension class") +} + +actual fun Dimension.Companion.of(dim: UInt): Dimension{ + return when(dim){ + 1u -> D1 + 2u -> D2 + 3u -> D3 + else -> object : Dimension { + override val dim: UInt get() = dim + } + } +} \ No newline at end of file diff --git a/kmath-dimensions/src/nativeMain/kotlin/kscience/kmath/dimensions/dimNative.kt b/kmath-dimensions/src/nativeMain/kotlin/kscience/kmath/dimensions/dimNative.kt deleted file mode 100644 index aeaeaf759..000000000 --- a/kmath-dimensions/src/nativeMain/kotlin/kscience/kmath/dimensions/dimNative.kt +++ /dev/null @@ -1,20 +0,0 @@ -package kscience.kmath.dimensions - -import kotlin.native.concurrent.ThreadLocal -import kotlin.reflect.KClass - -@ThreadLocal -private val dimensionMap: MutableMap = hashMapOf(1u to D1, 2u to D2, 3u to D3) - -@Suppress("UNCHECKED_CAST") -public actual fun Dimension.Companion.resolve(type: KClass): D = dimensionMap - .entries - .map(MutableMap.MutableEntry::value) - .find { it::class == type } as? D - ?: error("Can't resolve dimension $type") - -public actual fun Dimension.Companion.of(dim: UInt): Dimension = dimensionMap.getOrPut(dim) { - object : Dimension { - override val dim: UInt get() = dim - } -} diff --git a/kmath-ejml/build.gradle.kts b/kmath-ejml/build.gradle.kts deleted file mode 100644 index fa4aa3e39..000000000 --- a/kmath-ejml/build.gradle.kts +++ /dev/null @@ -1,8 +0,0 @@ -plugins { - id("ru.mipt.npm.jvm") -} - -dependencies { - implementation("org.ejml:ejml-simple:0.39") - implementation(project(":kmath-core")) -} diff --git a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrix.kt b/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrix.kt deleted file mode 100644 index ed6b1571e..000000000 --- a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrix.kt +++ /dev/null @@ -1,71 +0,0 @@ -package kscience.kmath.ejml - -import org.ejml.dense.row.factory.DecompositionFactory_DDRM -import org.ejml.simple.SimpleMatrix -import kscience.kmath.linear.DeterminantFeature -import kscience.kmath.linear.FeaturedMatrix -import kscience.kmath.linear.LUPDecompositionFeature -import kscience.kmath.linear.MatrixFeature -import kscience.kmath.structures.NDStructure - -/** - * Represents featured matrix over EJML [SimpleMatrix]. - * - * @property origin the underlying [SimpleMatrix]. - * @author Iaroslav Postovalov - */ -public class EjmlMatrix(public val origin: SimpleMatrix, features: Set? = null) : FeaturedMatrix { - public override val rowNum: Int - get() = origin.numRows() - - public override val colNum: Int - get() = origin.numCols() - - public override val shape: IntArray - get() = intArrayOf(origin.numRows(), origin.numCols()) - - public override val features: Set = setOf( - object : LUPDecompositionFeature, DeterminantFeature { - override val determinant: Double - get() = origin.determinant() - - private val lup by lazy { - val ludecompositionF64 = DecompositionFactory_DDRM.lu(origin.numRows(), origin.numCols()) - .also { it.decompose(origin.ddrm.copy()) } - - Triple( - EjmlMatrix(SimpleMatrix(ludecompositionF64.getRowPivot(null))), - EjmlMatrix(SimpleMatrix(ludecompositionF64.getLower(null))), - EjmlMatrix(SimpleMatrix(ludecompositionF64.getUpper(null))), - ) - } - - override val l: FeaturedMatrix - get() = lup.second - - override val u: FeaturedMatrix - get() = lup.third - - override val p: FeaturedMatrix - get() = lup.first - } - ) union features.orEmpty() - - public override fun suggestFeature(vararg features: MatrixFeature): EjmlMatrix = - EjmlMatrix(origin, this.features + features) - - public override operator fun get(i: Int, j: Int): Double = origin[i, j] - - public override fun equals(other: Any?): Boolean { - if (other is EjmlMatrix) return origin.isIdentical(other.origin, 0.0) - return NDStructure.equals(this, other as? NDStructure<*> ?: return false) - } - - public override fun hashCode(): Int { - var result = origin.hashCode() - result = 31 * result + features.hashCode() - return result - } - - public override fun toString(): String = "EjmlMatrix(origin=$origin, features=$features)" -} diff --git a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrixContext.kt b/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrixContext.kt deleted file mode 100644 index 31792e39c..000000000 --- a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlMatrixContext.kt +++ /dev/null @@ -1,84 +0,0 @@ -package kscience.kmath.ejml - -import kscience.kmath.linear.MatrixContext -import kscience.kmath.linear.Point -import kscience.kmath.structures.Matrix -import org.ejml.simple.SimpleMatrix - -/** - * Converts this matrix to EJML one. - */ -public fun Matrix.toEjml(): EjmlMatrix = - if (this is EjmlMatrix) this else EjmlMatrixContext.produce(rowNum, colNum) { i, j -> get(i, j) } - -/** - * Represents context of basic operations operating with [EjmlMatrix]. - * - * @author Iaroslav Postovalov - */ -public object EjmlMatrixContext : MatrixContext { - - /** - * Converts this vector to EJML one. - */ - public fun Point.toEjml(): EjmlVector = - if (this is EjmlVector) this else EjmlVector(SimpleMatrix(size, 1).also { - (0 until it.numRows()).forEach { row -> it[row, 0] = get(row) } - }) - - override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): EjmlMatrix = - EjmlMatrix(SimpleMatrix(rows, columns).also { - (0 until it.numRows()).forEach { row -> - (0 until it.numCols()).forEach { col -> it[row, col] = initializer(row, col) } - } - }) - - public override fun Matrix.dot(other: Matrix): EjmlMatrix = - EjmlMatrix(toEjml().origin.mult(other.toEjml().origin)) - - public override fun Matrix.dot(vector: Point): EjmlVector = - EjmlVector(toEjml().origin.mult(vector.toEjml().origin)) - - public override fun add(a: Matrix, b: Matrix): EjmlMatrix = - EjmlMatrix(a.toEjml().origin + b.toEjml().origin) - - public override operator fun Matrix.minus(b: Matrix): EjmlMatrix = - EjmlMatrix(toEjml().origin - b.toEjml().origin) - - public override fun multiply(a: Matrix, k: Number): EjmlMatrix = - produce(a.rowNum, a.colNum) { i, j -> a[i, j] * k.toDouble() } - - public override operator fun Matrix.times(value: Double): EjmlMatrix = - EjmlMatrix(toEjml().origin.scale(value)) -} - -/** - * Solves for X in the following equation: x = a^-1*b, where 'a' is base matrix and 'b' is an n by p matrix. - * - * @param a the base matrix. - * @param b n by p matrix. - * @return the solution for 'x' that is n by p. - * @author Iaroslav Postovalov - */ -public fun EjmlMatrixContext.solve(a: Matrix, b: Matrix): EjmlMatrix = - EjmlMatrix(a.toEjml().origin.solve(b.toEjml().origin)) - -/** - * Solves for X in the following equation: x = a^(-1)*b, where 'a' is base matrix and 'b' is an n by p matrix. - * - * @param a the base matrix. - * @param b n by p vector. - * @return the solution for 'x' that is n by p. - * @author Iaroslav Postovalov - */ -public fun EjmlMatrixContext.solve(a: Matrix, b: Point): EjmlVector = - EjmlVector(a.toEjml().origin.solve(b.toEjml().origin)) - -/** - * Returns the inverse of given matrix: b = a^(-1). - * - * @param a the matrix. - * @return the inverse of this matrix. - * @author Iaroslav Postovalov - */ -public fun EjmlMatrixContext.inverse(a: Matrix): EjmlMatrix = EjmlMatrix(a.toEjml().origin.invert()) diff --git a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlVector.kt b/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlVector.kt deleted file mode 100644 index f7cd1b66d..000000000 --- a/kmath-ejml/src/main/kotlin/kscience/kmath/ejml/EjmlVector.kt +++ /dev/null @@ -1,40 +0,0 @@ -package kscience.kmath.ejml - -import org.ejml.simple.SimpleMatrix -import kscience.kmath.linear.Point -import kscience.kmath.structures.Buffer - -/** - * Represents point over EJML [SimpleMatrix]. - * - * @property origin the underlying [SimpleMatrix]. - * @author Iaroslav Postovalov - */ -public class EjmlVector internal constructor(public val origin: SimpleMatrix) : Point { - public override val size: Int - get() = origin.numRows() - - init { - require(origin.numCols() == 1) { "Only single column matrices are allowed" } - } - - public override operator fun get(index: Int): Double = origin[index] - - public override operator fun iterator(): Iterator = object : Iterator { - private var cursor: Int = 0 - - override fun next(): Double { - cursor += 1 - return origin[cursor - 1] - } - - override fun hasNext(): Boolean = cursor < origin.numCols() * origin.numRows() - } - - public override fun contentEquals(other: Buffer<*>): Boolean { - if (other is EjmlVector) return origin.isIdentical(other.origin, 0.0) - return super.contentEquals(other) - } - - public override fun toString(): String = "EjmlVector(origin=$origin)" -} diff --git a/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlMatrixTest.kt b/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlMatrixTest.kt deleted file mode 100644 index e0f15be83..000000000 --- a/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlMatrixTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -package kscience.kmath.ejml - -import kscience.kmath.linear.DeterminantFeature -import kscience.kmath.linear.LUPDecompositionFeature -import kscience.kmath.linear.MatrixFeature -import kscience.kmath.linear.getFeature -import org.ejml.dense.row.factory.DecompositionFactory_DDRM -import org.ejml.simple.SimpleMatrix -import kotlin.random.Random -import kotlin.random.asJavaRandom -import kotlin.test.* - -internal class EjmlMatrixTest { - private val random = Random(0) - - private val randomMatrix: SimpleMatrix - get() { - val s = random.nextInt(2, 100) - return SimpleMatrix.random_DDRM(s, s, 0.0, 10.0, random.asJavaRandom()) - } - - @Test - fun rowNum() { - val m = randomMatrix - assertEquals(m.numRows(), EjmlMatrix(m).rowNum) - } - - @Test - fun colNum() { - val m = randomMatrix - assertEquals(m.numCols(), EjmlMatrix(m).rowNum) - } - - @Test - fun shape() { - val m = randomMatrix - val w = EjmlMatrix(m) - assertEquals(listOf(m.numRows(), m.numCols()), w.shape.toList()) - } - - @Test - fun features() { - val m = randomMatrix - val w = EjmlMatrix(m) - val det = w.getFeature>() ?: fail() - assertEquals(m.determinant(), det.determinant) - val lup = w.getFeature>() ?: fail() - - val ludecompositionF64 = DecompositionFactory_DDRM.lu(m.numRows(), m.numCols()) - .also { it.decompose(m.ddrm.copy()) } - - assertEquals(EjmlMatrix(SimpleMatrix(ludecompositionF64.getLower(null))), lup.l) - assertEquals(EjmlMatrix(SimpleMatrix(ludecompositionF64.getUpper(null))), lup.u) - assertEquals(EjmlMatrix(SimpleMatrix(ludecompositionF64.getRowPivot(null))), lup.p) - } - - private object SomeFeature : MatrixFeature {} - - @Test - fun suggestFeature() { - assertNotNull(EjmlMatrix(randomMatrix).suggestFeature(SomeFeature).getFeature()) - } - - @Test - fun get() { - val m = randomMatrix - assertEquals(m[0, 0], EjmlMatrix(m)[0, 0]) - } - - @Test - fun origin() { - val m = randomMatrix - assertSame(m, EjmlMatrix(m).origin) - } -} diff --git a/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlVectorTest.kt b/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlVectorTest.kt deleted file mode 100644 index e27f977d2..000000000 --- a/kmath-ejml/src/test/kotlin/kscience/kmath/ejml/EjmlVectorTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package kscience.kmath.ejml - -import org.ejml.simple.SimpleMatrix -import kotlin.random.Random -import kotlin.random.asJavaRandom -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertSame - -internal class EjmlVectorTest { - private val random = Random(0) - - private val randomMatrix: SimpleMatrix - get() = SimpleMatrix.random_DDRM(random.nextInt(2, 100), 1, 0.0, 10.0, random.asJavaRandom()) - - @Test - fun size() { - val m = randomMatrix - val w = EjmlVector(m) - assertEquals(m.numRows(), w.size) - } - - @Test - fun get() { - val m = randomMatrix - val w = EjmlVector(m) - assertEquals(m[0, 0], w[0]) - } - - @Test - fun iterator() { - val m = randomMatrix - val w = EjmlVector(m) - - assertEquals( - m.iterator(true, 0, 0, m.numRows() - 1, 0).asSequence().toList(), - w.iterator().asSequence().toList() - ) - } - - @Test - fun origin() { - val m = randomMatrix - val w = EjmlVector(m) - assertSame(m, w.origin) - } -} diff --git a/kmath-for-real/README.md b/kmath-for-real/README.md deleted file mode 100644 index 2ddf78e57..000000000 --- a/kmath-for-real/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Real number specialization module (`kmath-for-real`) - - - [RealVector](src/commonMain/kotlin/kscience/kmath/real/RealVector.kt) : Numpy-like operations for Buffers/Points - - [RealMatrix](src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt) : Numpy-like operations for 2d real structures - - [grids](src/commonMain/kotlin/kscience/kmath/structures/grids.kt) : Uniform grid generators - - -> #### Artifact: -> -> This module artifact: `kscience.kmath:kmath-for-real:0.2.0-dev-4`. -> -> Bintray release version: [ ![Download](https://api.bintray.com/packages/mipt-npm/kscience/kmath-for-real/images/download.svg) ](https://bintray.com/mipt-npm/kscience/kmath-for-real/_latestVersion) -> -> Bintray development version: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-for-real/images/download.svg) ](https://bintray.com/mipt-npm/dev/kmath-for-real/_latestVersion) -> -> **Gradle:** -> -> ```gradle -> repositories { -> maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } -> maven { url 'https://dl.bintray.com/mipt-npm/kscience' } -> maven { url 'https://dl.bintray.com/mipt-npm/dev' } -> maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - -> } -> -> dependencies { -> implementation 'kscience.kmath:kmath-for-real:0.2.0-dev-4' -> } -> ``` -> **Gradle Kotlin DSL:** -> -> ```kotlin -> repositories { -> maven("https://dl.bintray.com/kotlin/kotlin-eap") -> maven("https://dl.bintray.com/mipt-npm/kscience") -> maven("https://dl.bintray.com/mipt-npm/dev") -> maven("https://dl.bintray.com/hotkeytlt/maven") -> } -> -> dependencies { -> implementation("kscience.kmath:kmath-for-real:0.2.0-dev-4") -> } -> ``` diff --git a/kmath-for-real/build.gradle.kts b/kmath-for-real/build.gradle.kts index f26f98c2c..a8a8975bc 100644 --- a/kmath-for-real/build.gradle.kts +++ b/kmath-for-real/build.gradle.kts @@ -1,37 +1,11 @@ plugins { - id("ru.mipt.npm.mpp") + id("scientifik.mpp") } -kotlin.sourceSets.commonMain { - dependencies { - api(project(":kmath-core")) +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-core")) + } } -} - -readme { - description = """ - Extension module that should be used to achieve numpy-like behavior. - All operations are specialized to work with `Double` numbers without declaring algebraic contexts. - One can still use generic algebras though. - """.trimIndent() - maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL - propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) - - feature( - id = "RealVector", - description = "Numpy-like operations for Buffers/Points", - ref = "src/commonMain/kotlin/kscience/kmath/real/RealVector.kt" - ) - - feature( - id = "RealMatrix", - description = "Numpy-like operations for 2d real structures", - ref = "src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt" - ) - - feature( - id = "grids", - description = "Uniform grid generators", - ref = "src/commonMain/kotlin/kscience/kmath/structures/grids.kt" - ) -} +} \ No newline at end of file diff --git a/kmath-for-real/docs/README-TEMPLATE.md b/kmath-for-real/docs/README-TEMPLATE.md deleted file mode 100644 index 670844bd0..000000000 --- a/kmath-for-real/docs/README-TEMPLATE.md +++ /dev/null @@ -1,5 +0,0 @@ -# Real number specialization module (`kmath-for-real`) - -${features} - -${artifact} diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt deleted file mode 100644 index e8ad835e5..000000000 --- a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt +++ /dev/null @@ -1,180 +0,0 @@ -package kscience.kmath.real - -import kscience.kmath.linear.FeaturedMatrix -import kscience.kmath.linear.MatrixContext -import kscience.kmath.linear.RealMatrixContext.elementContext -import kscience.kmath.linear.VirtualMatrix -import kscience.kmath.linear.inverseWithLUP -import kscience.kmath.misc.UnstableKMathAPI -import kscience.kmath.operations.invoke -import kscience.kmath.operations.sum -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.RealBuffer -import kscience.kmath.structures.asIterable -import kotlin.math.pow - -/* - * Functions for convenient "numpy-like" operations with Double matrices. - * - * Initial implementation of these functions is taken from: - * https://github.com/thomasnield/numky/blob/master/src/main/kotlin/org/nield/numky/linear/DoubleOperators.kt - * - */ - -/* - * Functions that help create a real (Double) matrix - */ - -public typealias RealMatrix = FeaturedMatrix - -public fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double): RealMatrix = - MatrixContext.real.produce(rowNum, colNum, initializer) - -public fun Array.toMatrix(): RealMatrix { - return MatrixContext.real.produce(size, this[0].size) { row, col -> this[row][col] } -} - -public fun Sequence.toMatrix(): RealMatrix = toList().let { - MatrixContext.real.produce(it.size, it[0].size) { row, col -> it[row][col] } -} - -public fun RealMatrix.repeatStackVertical(n: Int): RealMatrix = - VirtualMatrix(rowNum * n, colNum) { row, col -> - get(if (row == 0) 0 else row % rowNum, col) - } - -/* - * Operations for matrix and real number - */ - -public operator fun RealMatrix.times(double: Double): RealMatrix = - MatrixContext.real.produce(rowNum, colNum) { row, col -> - this[row, col] * double - } - -public operator fun RealMatrix.plus(double: Double): RealMatrix = - MatrixContext.real.produce(rowNum, colNum) { row, col -> - this[row, col] + double - } - -public operator fun RealMatrix.minus(double: Double): RealMatrix = - MatrixContext.real.produce(rowNum, colNum) { row, col -> - this[row, col] - double - } - -public operator fun RealMatrix.div(double: Double): RealMatrix = - MatrixContext.real.produce(rowNum, colNum) { row, col -> - this[row, col] / double - } - -public operator fun Double.times(matrix: RealMatrix): RealMatrix = - MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - this * matrix[row, col] - } - -public operator fun Double.plus(matrix: RealMatrix): RealMatrix = - MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - this + matrix[row, col] - } - -public operator fun Double.minus(matrix: RealMatrix): RealMatrix = - MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - this - matrix[row, col] - } - -// TODO: does this operation make sense? Should it be 'this/matrix[row, col]'? -//operator fun Double.div(matrix: RealMatrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { -// row, col -> matrix[row, col] / this -//} - -/* - * Operations on two matrices (per-element!) - */ - -@UnstableKMathAPI -public operator fun RealMatrix.times(other: RealMatrix): RealMatrix = - MatrixContext.real.produce(rowNum, colNum) { row, col -> this[row, col] * other[row, col] } - -public operator fun RealMatrix.plus(other: RealMatrix): RealMatrix = - MatrixContext.real.add(this, other) - -public operator fun RealMatrix.minus(other: RealMatrix): RealMatrix = - MatrixContext.real.produce(rowNum, colNum) { row, col -> this[row, col] - other[row, col] } - -/* - * Operations on columns - */ - -public inline fun RealMatrix.appendColumn(crossinline mapper: (Buffer) -> Double): RealMatrix = - MatrixContext.real.produce(rowNum, colNum + 1) { row, col -> - if (col < colNum) - this[row, col] - else - mapper(rows[row]) - } - -public fun RealMatrix.extractColumns(columnRange: IntRange): RealMatrix = - MatrixContext.real.produce(rowNum, columnRange.count()) { row, col -> - this[row, columnRange.first + col] - } - -public fun RealMatrix.extractColumn(columnIndex: Int): RealMatrix = - extractColumns(columnIndex..columnIndex) - -public fun RealMatrix.sumByColumn(): RealBuffer = RealBuffer(colNum) { j -> - val column = columns[j] - elementContext { sum(column.asIterable()) } -} - -public fun RealMatrix.minByColumn(): RealBuffer = RealBuffer(colNum) { j -> - columns[j].asIterable().minOrNull() ?: error("Cannot produce min on empty column") -} - -public fun RealMatrix.maxByColumn(): RealBuffer = RealBuffer(colNum) { j -> - columns[j].asIterable().maxOrNull() ?: error("Cannot produce min on empty column") -} - -public fun RealMatrix.averageByColumn(): RealBuffer = RealBuffer(colNum) { j -> - columns[j].asIterable().average() -} - -/* - * Operations processing all elements - */ - -public fun RealMatrix.sum(): Double = elements().map { (_, value) -> value }.sum() -public fun RealMatrix.min(): Double? = elements().map { (_, value) -> value }.minOrNull() -public fun RealMatrix.max(): Double? = elements().map { (_, value) -> value }.maxOrNull() -public fun RealMatrix.average(): Double = elements().map { (_, value) -> value }.average() - -public inline fun RealMatrix.map(transform: (Double) -> Double): RealMatrix = - MatrixContext.real.produce(rowNum, colNum) { i, j -> - transform(get(i, j)) - } - -/** - * Inverse a square real matrix using LUP decomposition - */ -public fun RealMatrix.inverseWithLUP(): RealMatrix = MatrixContext.real.inverseWithLUP(this) - -//extended operations - -public fun RealMatrix.pow(p: Double): RealMatrix = map { it.pow(p) } - -public fun RealMatrix.pow(p: Int): RealMatrix = map { it.pow(p) } - -public fun exp(arg: RealMatrix): RealMatrix = arg.map { kotlin.math.exp(it) } - -public fun sqrt(arg: RealMatrix): RealMatrix = arg.map { kotlin.math.sqrt(it) } - -public fun RealMatrix.square(): RealMatrix = map { it.pow(2) } - -public fun sin(arg: RealMatrix): RealMatrix = arg.map { kotlin.math.sin(it) } - -public fun cos(arg: RealMatrix): RealMatrix = arg.map { kotlin.math.cos(it) } - -public fun tan(arg: RealMatrix): RealMatrix = arg.map { kotlin.math.tan(it) } - -public fun ln(arg: RealMatrix): RealMatrix = arg.map { kotlin.math.ln(it) } - -public fun log10(arg: RealMatrix): RealMatrix = arg.map { kotlin.math.log10(it) } \ No newline at end of file diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealVector.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealVector.kt deleted file mode 100644 index 596692782..000000000 --- a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealVector.kt +++ /dev/null @@ -1,82 +0,0 @@ -package kscience.kmath.real - -import kscience.kmath.linear.Point -import kscience.kmath.operations.Norm -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.asBuffer -import kscience.kmath.structures.asIterable -import kotlin.math.pow -import kotlin.math.sqrt - -public typealias RealVector = Point - -public object VectorL2Norm : Norm, Double> { - override fun norm(arg: Point): Double = sqrt(arg.asIterable().sumByDouble(Number::toDouble)) -} - -public operator fun Buffer.Companion.invoke(vararg doubles: Double): RealVector = doubles.asBuffer() - -/** - * Fill the vector of given [size] with given [value] - */ -public fun Buffer.Companion.same(size: Int, value: Number): RealVector = real(size) { value.toDouble() } - -// Transformation methods - -public inline fun RealVector.map(transform: (Double) -> Double): RealVector = - Buffer.real(size) { transform(get(it)) } - -public inline fun RealVector.mapIndexed(transform: (index: Int, value: Double) -> Double): RealVector = - Buffer.real(size) { transform(it, get(it)) } - -public operator fun RealVector.plus(other: RealVector): RealVector = - mapIndexed { index, value -> value + other[index] } - -public operator fun RealVector.plus(number: Number): RealVector = map { it + number.toDouble() } - -public operator fun Number.plus(vector: RealVector): RealVector = vector + this - -public operator fun RealVector.unaryMinus(): Buffer = map { -it } - -public operator fun RealVector.minus(other: RealVector): RealVector = - mapIndexed { index, value -> value - other[index] } - -public operator fun RealVector.minus(number: Number): RealVector = map { it - number.toDouble() } - -public operator fun Number.minus(vector: RealVector): RealVector = vector.map { toDouble() - it } - -public operator fun RealVector.times(other: RealVector): RealVector = - mapIndexed { index, value -> value * other[index] } - -public operator fun RealVector.times(number: Number): RealVector = map { it * number.toDouble() } - -public operator fun Number.times(vector: RealVector): RealVector = vector * this - -public operator fun RealVector.div(other: RealVector): RealVector = - mapIndexed { index, value -> value / other[index] } - -public operator fun RealVector.div(number: Number): RealVector = map { it / number.toDouble() } - -public operator fun Number.div(vector: RealVector): RealVector = vector.map { toDouble() / it } - -//extended operations - -public fun RealVector.pow(p: Double): RealVector = map { it.pow(p) } - -public fun RealVector.pow(p: Int): RealVector = map { it.pow(p) } - -public fun exp(vector: RealVector): RealVector = vector.map { kotlin.math.exp(it) } - -public fun sqrt(vector: RealVector): RealVector = vector.map { kotlin.math.sqrt(it) } - -public fun RealVector.square(): RealVector = map { it.pow(2) } - -public fun sin(vector: RealVector): RealVector = vector.map { kotlin.math.sin(it) } - -public fun cos(vector: RealVector): RealVector = vector.map { kotlin.math.cos(it) } - -public fun tan(vector: RealVector): RealVector = vector.map { kotlin.math.tan(it) } - -public fun ln(vector: RealVector): RealVector = vector.map { kotlin.math.ln(it) } - -public fun log10(vector: RealVector): RealVector = vector.map { kotlin.math.log10(it) } diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/dot.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/dot.kt deleted file mode 100644 index 9beffe6bb..000000000 --- a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/dot.kt +++ /dev/null @@ -1,31 +0,0 @@ -package kscience.kmath.real - -import kscience.kmath.linear.BufferMatrix -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.RealBuffer - - -/** - * Optimized dot product for real matrices - */ -public infix fun BufferMatrix.dot(other: BufferMatrix): BufferMatrix { - require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" } - val resultArray = DoubleArray(this.rowNum * other.colNum) - - //convert to array to insure there is no memory indirection - fun Buffer.unsafeArray() = if (this is RealBuffer) - this.array - else - DoubleArray(size) { get(it) } - - val a = this.buffer.unsafeArray() - val b = other.buffer.unsafeArray() - - for (i in (0 until rowNum)) - for (j in (0 until other.colNum)) - for (k in (0 until colNum)) - resultArray[i * other.colNum + j] += a[i * colNum + k] * b[k * other.colNum + j] - - val buffer = RealBuffer(resultArray) - return BufferMatrix(rowNum, other.colNum, buffer) -} \ No newline at end of file diff --git a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/RealVector.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/RealVector.kt new file mode 100644 index 000000000..2b89904e3 --- /dev/null +++ b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/RealVector.kt @@ -0,0 +1,52 @@ +package scientifik.kmath.real + +import scientifik.kmath.linear.BufferVectorSpace +import scientifik.kmath.linear.Point +import scientifik.kmath.linear.VectorSpace +import scientifik.kmath.operations.Norm +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.SpaceElement +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.RealBuffer +import scientifik.kmath.structures.asBuffer +import scientifik.kmath.structures.asIterable +import kotlin.math.sqrt + +typealias RealPoint = Point + +fun DoubleArray.asVector() = RealVector(this.asBuffer()) +fun List.asVector() = RealVector(this.asBuffer()) + +object VectorL2Norm : Norm, Double> { + override fun norm(arg: Point): Double = sqrt(arg.asIterable().sumByDouble { it.toDouble() }) +} + +inline class RealVector(private val point: Point) : + SpaceElement>, RealPoint { + + override val context: VectorSpace get() = space(point.size) + + override fun unwrap(): RealPoint = point + + override fun RealPoint.wrap(): RealVector = RealVector(this) + + override val size: Int get() = point.size + + override fun get(index: Int): Double = point[index] + + override fun iterator(): Iterator = point.iterator() + + companion object { + + private val spaceCache = HashMap>() + + inline operator fun invoke(dim: Int, initializer: (Int) -> Double) = + RealVector(RealBuffer(dim, initializer)) + + operator fun invoke(vararg values: Double): RealVector = values.asVector() + + fun space(dim: Int): BufferVectorSpace = spaceCache.getOrPut(dim) { + BufferVectorSpace(dim, RealField) { size, init -> Buffer.real(size, init) } + } + } +} \ No newline at end of file diff --git a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realBuffer.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realBuffer.kt new file mode 100644 index 000000000..82c0e86b2 --- /dev/null +++ b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realBuffer.kt @@ -0,0 +1,8 @@ +package scientifik.kmath.real + +import scientifik.kmath.structures.RealBuffer + +/** + * Simplified [RealBuffer] to array comparison + */ +fun RealBuffer.contentEquals(vararg doubles: Double) = array.contentEquals(doubles) \ No newline at end of file diff --git a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realMatrix.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realMatrix.kt new file mode 100644 index 000000000..65f86eec7 --- /dev/null +++ b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realMatrix.kt @@ -0,0 +1,165 @@ +package scientifik.kmath.real + +import scientifik.kmath.linear.MatrixContext +import scientifik.kmath.linear.RealMatrixContext.elementContext +import scientifik.kmath.linear.VirtualMatrix +import scientifik.kmath.operations.sum +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.RealBuffer +import scientifik.kmath.structures.asIterable +import kotlin.math.pow + +/* + * Functions for convenient "numpy-like" operations with Double matrices. + * + * Initial implementation of these functions is taken from: + * https://github.com/thomasnield/numky/blob/master/src/main/kotlin/org/nield/numky/linear/DoubleOperators.kt + * + */ + +/* + * Functions that help create a real (Double) matrix + */ + +typealias RealMatrix = Matrix + +fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum, initializer) + +fun Array.toMatrix(): RealMatrix{ + return MatrixContext.real.produce(size, this[0].size) { row, col -> this[row][col] } +} + +fun Sequence.toMatrix(): RealMatrix = toList().let { + MatrixContext.real.produce(it.size, it[0].size) { row, col -> it[row][col] } +} + +fun Matrix.repeatStackVertical(n: Int): RealMatrix = + VirtualMatrix(rowNum * n, colNum) { row, col -> + get(if (row == 0) 0 else row % rowNum, col) + } + +/* + * Operations for matrix and real number + */ + +operator fun Matrix.times(double: Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] * double + } + +operator fun Matrix.plus(double: Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] + double + } + +operator fun Matrix.minus(double: Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] - double + } + +operator fun Matrix.div(double: Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] / double + } + +operator fun Double.times(matrix: Matrix): RealMatrix = + MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + this * matrix[row, col] + } + +operator fun Double.plus(matrix: Matrix): RealMatrix = + MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + this + matrix[row, col] + } + +operator fun Double.minus(matrix: Matrix): RealMatrix = + MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + this - matrix[row, col] + } + +// TODO: does this operation make sense? Should it be 'this/matrix[row, col]'? +//operator fun Double.div(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { +// row, col -> matrix[row, col] / this +//} + +/* + * Per-element (!) square and power operations + */ + +fun Matrix.square(): RealMatrix = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col].pow(2) +} + +fun Matrix.pow(n: Int): RealMatrix = MatrixContext.real.produce(rowNum, colNum) { i, j -> + this[i, j].pow(n) +} + +/* + * Operations on two matrices (per-element!) + */ + +operator fun Matrix.times(other: Matrix): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] * other[row, col] + } + +operator fun Matrix.plus(other: Matrix): RealMatrix = + MatrixContext.real.add(this, other) + +operator fun Matrix.minus(other: Matrix): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] - other[row, col] + } + +/* + * Operations on columns + */ + +inline fun Matrix.appendColumn(crossinline mapper: (Buffer) -> Double) = + MatrixContext.real.produce(rowNum, colNum + 1) { row, col -> + if (col < colNum) + this[row, col] + else + mapper(rows[row]) + } + +fun Matrix.extractColumns(columnRange: IntRange): RealMatrix = + MatrixContext.real.produce(rowNum, columnRange.count()) { row, col -> + this[row, columnRange.first + col] + } + +fun Matrix.extractColumn(columnIndex: Int): RealMatrix = + extractColumns(columnIndex..columnIndex) + +fun Matrix.sumByColumn(): RealBuffer = RealBuffer(colNum) { j -> + val column = columns[j] + with(elementContext) { + sum(column.asIterable()) + } +} + +fun Matrix.minByColumn(): RealBuffer = RealBuffer(colNum) { j -> + columns[j].asIterable().min() ?: throw Exception("Cannot produce min on empty column") +} + +fun Matrix.maxByColumn(): RealBuffer = RealBuffer(colNum) { j -> + columns[j].asIterable().max() ?: throw Exception("Cannot produce min on empty column") +} + +fun Matrix.averageByColumn(): RealBuffer = RealBuffer(colNum) { j -> + columns[j].asIterable().average() +} + +/* + * Operations processing all elements + */ + +fun Matrix.sum() = elements().map { (_, value) -> value }.sum() + +fun Matrix.min() = elements().map { (_, value) -> value }.min() + +fun Matrix.max() = elements().map { (_, value) -> value }.max() + +fun Matrix.average() = elements().map { (_, value) -> value }.average() diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/GridTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/GridTest.kt deleted file mode 100644 index 5f19e94b7..000000000 --- a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/GridTest.kt +++ /dev/null @@ -1,13 +0,0 @@ -package kaceince.kmath.real - -import kscience.kmath.real.step -import kotlin.test.Test -import kotlin.test.assertEquals - -class GridTest { - @Test - fun testStepGrid(){ - val grid = 0.0..1.0 step 0.2 - assertEquals(6, grid.size) - } -} \ No newline at end of file diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt deleted file mode 100644 index 6215ba5e8..000000000 --- a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt +++ /dev/null @@ -1,37 +0,0 @@ -package kaceince.kmath.real - -import kscience.kmath.linear.* -import kscience.kmath.operations.invoke -import kscience.kmath.real.RealVector -import kscience.kmath.real.plus -import kscience.kmath.structures.Buffer -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class RealVectorTest { - @Test - fun testSum() { - val vector1 = Buffer.real(5) { it.toDouble() } - val vector2 = Buffer.real(5) { 5 - it.toDouble() } - val sum = vector1 + vector2 - assertEquals(5.0, sum[2]) - } - - @Test - fun testVectorToMatrix() { - val vector = Buffer.real(5) { it.toDouble() } - val matrix = vector.asMatrix() - assertEquals(4.0, matrix[4, 0]) - } - - @Test - fun testDot() { - val vector1 = Buffer.real(5) { it.toDouble() } - val vector2 = Buffer.real(5) { 5 - it.toDouble() } - val matrix1 = vector1.asMatrix() - val matrix2 = vector2.asMatrix().transpose() - val product = MatrixContext.real { matrix1 dot matrix2 } - assertEquals(5.0, product[1, 0]) - assertEquals(6.0, product[2, 2]) - } -} diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt b/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt similarity index 94% rename from kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt rename to kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt index 5c33b76a9..8918fb300 100644 --- a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt @@ -1,15 +1,14 @@ -package kaceince.kmath.real +package scientific.kmath.real -import kscience.kmath.linear.VirtualMatrix -import kscience.kmath.linear.build -import kscience.kmath.real.* -import kscience.kmath.structures.Matrix -import kscience.kmath.structures.contentEquals +import scientifik.kmath.linear.VirtualMatrix +import scientifik.kmath.linear.build +import scientifik.kmath.real.* +import scientifik.kmath.structures.Matrix import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -internal class RealMatrixTest { +class RealMatrixTest { @Test fun testSum() { val m = realMatrix(10, 10) { i, j -> (i + j).toDouble() } diff --git a/kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt b/kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt new file mode 100644 index 000000000..28e62b066 --- /dev/null +++ b/kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt @@ -0,0 +1,37 @@ +package scientifik.kmath.linear + +import scientifik.kmath.real.RealVector +import kotlin.test.Test +import kotlin.test.assertEquals + +class VectorTest { + @Test + fun testSum() { + val vector1 = RealVector(5) { it.toDouble() } + val vector2 = RealVector(5) { 5 - it.toDouble() } + val sum = vector1 + vector2 + assertEquals(5.0, sum[2]) + } + + @Test + fun testVectorToMatrix() { + val vector = RealVector(5) { it.toDouble() } + val matrix = vector.asMatrix() + assertEquals(4.0, matrix[4, 0]) + } + + @Test + fun testDot() { + val vector1 = RealVector(5) { it.toDouble() } + val vector2 = RealVector(5) { 5 - it.toDouble() } + + val matrix1 = vector1.asMatrix() + val matrix2 = vector2.asMatrix().transpose() + val product = MatrixContext.real.run { matrix1 dot matrix2 } + + + assertEquals(5.0, product[1, 0]) + assertEquals(6.0, product[2, 2]) + } + +} \ No newline at end of file diff --git a/kmath-functions/build.gradle.kts b/kmath-functions/build.gradle.kts index 2a4539c10..4c158a32e 100644 --- a/kmath-functions/build.gradle.kts +++ b/kmath-functions/build.gradle.kts @@ -1,9 +1,11 @@ plugins { - id("ru.mipt.npm.mpp") + id("scientifik.mpp") } -kotlin.sourceSets.commonMain { - dependencies { - api(project(":kmath-core")) +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-core")) + } } } diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Polynomial.kt deleted file mode 100644 index 820076c4c..000000000 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Polynomial.kt +++ /dev/null @@ -1,66 +0,0 @@ -package kscience.kmath.functions - -import kscience.kmath.operations.Ring -import kscience.kmath.operations.Space -import kscience.kmath.operations.invoke -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract -import kotlin.math.max -import kotlin.math.pow - -/** - * Polynomial coefficients without fixation on specific context they are applied to - * @param coefficients constant is the leftmost coefficient - */ -public inline class Polynomial(public val coefficients: List) - -@Suppress("FunctionName") -public fun Polynomial(vararg coefficients: T): Polynomial = Polynomial(coefficients.toList()) - -public fun Polynomial.value(): Double = coefficients.reduceIndexed { index, acc, d -> acc + d.pow(index) } - -public fun > Polynomial.value(ring: C, arg: T): T = ring { - if (coefficients.isEmpty()) return@ring zero - var res = coefficients.first() - var powerArg = arg - - for (index in 1 until coefficients.size) { - res += coefficients[index] * powerArg - // recalculating power on each step to avoid power costs on long polynomials - powerArg *= arg - } - - res -} - -/** - * Represent the polynomial as a regular context-less function - */ -public fun > Polynomial.asFunction(ring: C): (T) -> T = { value(ring, it) } - -/** - * An algebra for polynomials - */ -public class PolynomialSpace>(private val ring: C) : Space> { - public override val zero: Polynomial = Polynomial(emptyList()) - - public override fun add(a: Polynomial, b: Polynomial): Polynomial { - val dim = max(a.coefficients.size, b.coefficients.size) - - return ring { - Polynomial(List(dim) { index -> - a.coefficients.getOrElse(index) { zero } + b.coefficients.getOrElse(index) { zero } - }) - } - } - - public override fun multiply(a: Polynomial, k: Number): Polynomial = - ring { Polynomial(List(a.coefficients.size) { index -> a.coefficients[index] * k }) } - - public operator fun Polynomial.invoke(arg: T): T = value(ring, arg) -} - -public inline fun , R> C.polynomial(block: PolynomialSpace.() -> R): R { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return PolynomialSpace(this).block() -} diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/Interpolator.kt b/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/Interpolator.kt deleted file mode 100644 index 0620b4aa8..000000000 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/Interpolator.kt +++ /dev/null @@ -1,45 +0,0 @@ -package kscience.kmath.interpolation - -import kscience.kmath.functions.PiecewisePolynomial -import kscience.kmath.functions.value -import kscience.kmath.operations.Ring -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.asBuffer - -public fun interface Interpolator { - public fun interpolate(points: XYPointSet): (X) -> Y -} - -public interface PolynomialInterpolator> : Interpolator { - public val algebra: Ring - - public fun getDefaultValue(): T = error("Out of bounds") - - public fun interpolatePolynomials(points: XYPointSet): PiecewisePolynomial - - override fun interpolate(points: XYPointSet): (T) -> T = { x -> - interpolatePolynomials(points).value(algebra, x) ?: getDefaultValue() - } -} - -public fun > PolynomialInterpolator.interpolatePolynomials( - x: Buffer, - y: Buffer -): PiecewisePolynomial { - val pointSet = BufferXYPointSet(x, y) - return interpolatePolynomials(pointSet) -} - -public fun > PolynomialInterpolator.interpolatePolynomials( - data: Map -): PiecewisePolynomial { - val pointSet = BufferXYPointSet(data.keys.toList().asBuffer(), data.values.toList().asBuffer()) - return interpolatePolynomials(pointSet) -} - -public fun > PolynomialInterpolator.interpolatePolynomials( - data: List> -): PiecewisePolynomial { - val pointSet = BufferXYPointSet(data.map { it.first }.asBuffer(), data.map { it.second }.asBuffer()) - return interpolatePolynomials(pointSet) -} diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/XYPointSet.kt b/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/XYPointSet.kt deleted file mode 100644 index 2abb7742c..000000000 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/XYPointSet.kt +++ /dev/null @@ -1,53 +0,0 @@ -package kscience.kmath.interpolation - -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.Structure2D - -public interface XYPointSet { - public val size: Int - public val x: Buffer - public val y: Buffer -} - -public interface XYZPointSet : XYPointSet { - public val z: Buffer -} - -internal fun > insureSorted(points: XYPointSet) { - for (i in 0 until points.size - 1) - require(points.x[i + 1] > points.x[i]) { "Input data is not sorted at index $i" } -} - -public class NDStructureColumn(public val structure: Structure2D, public val column: Int) : Buffer { - public override val size: Int - get() = structure.rowNum - - init { - require(column < structure.colNum) { "Column index is outside of structure column range" } - } - - public override operator fun get(index: Int): T = structure[index, column] - public override operator fun iterator(): Iterator = sequence { repeat(size) { yield(get(it)) } }.iterator() -} - -public class BufferXYPointSet( - public override val x: Buffer, - public override val y: Buffer -) : XYPointSet { - public override val size: Int - get() = x.size - - init { - require(x.size == y.size) { "Sizes of x and y buffers should be the same" } - } -} - -public fun Structure2D.asXYPointSet(): XYPointSet { - require(shape[1] == 2) { "Structure second dimension should be of size 2" } - - return object : XYPointSet { - override val size: Int get() = this@asXYPointSet.shape[0] - override val x: Buffer get() = NDStructureColumn(this@asXYPointSet, 0) - override val y: Buffer get() = NDStructureColumn(this@asXYPointSet, 1) - } -} diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Piecewise.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Piecewise.kt similarity index 56% rename from kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Piecewise.kt rename to kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Piecewise.kt index a8c020c05..16f8aa12b 100644 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Piecewise.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Piecewise.kt @@ -1,46 +1,48 @@ -package kscience.kmath.functions +package scientifik.kmath.functions -import kscience.kmath.operations.Ring +import scientifik.kmath.operations.Ring -public fun interface Piecewise { - public fun findPiece(arg: T): R? +interface Piecewise { + fun findPiece(arg: T): R? } -public fun interface PiecewisePolynomial : +interface PiecewisePolynomial : Piecewise> /** * Ordered list of pieces in piecewise function */ -public class OrderedPiecewisePolynomial>(delimiter: T) : +class OrderedPiecewisePolynomial>(delimeter: T) : PiecewisePolynomial { - private val delimiters: MutableList = arrayListOf(delimiter) - private val pieces: MutableList> = arrayListOf() + + private val delimiters: ArrayList = arrayListOf(delimeter) + private val pieces: ArrayList> = ArrayList() /** * Dynamically add a piece to the "right" side (beyond maximum argument value of previous piece) * @param right new rightmost position. If is less then current rightmost position, a error is thrown. */ - public fun putRight(right: T, piece: Polynomial) { + fun putRight(right: T, piece: Polynomial) { require(right > delimiters.last()) { "New delimiter should be to the right of old one" } delimiters.add(right) pieces.add(piece) } - public fun putLeft(left: T, piece: Polynomial) { + fun putLeft(left: T, piece: Polynomial) { require(left < delimiters.first()) { "New delimiter should be to the left of old one" } delimiters.add(0, left) pieces.add(0, piece) } override fun findPiece(arg: T): Polynomial? { - if (arg < delimiters.first() || arg >= delimiters.last()) + if (arg < delimiters.first() || arg >= delimiters.last()) { return null - else { - for (index in 1 until delimiters.size) - if (arg < delimiters[index]) + } else { + for (index in 1 until delimiters.size) { + if (arg < delimiters[index]) { return pieces[index - 1] - + } + } error("Piece not found") } } @@ -49,7 +51,7 @@ public class OrderedPiecewisePolynomial>(delimiter: T) : /** * Return a value of polynomial function with given [ring] an given [arg] or null if argument is outside of piecewise definition. */ -public fun , C : Ring> PiecewisePolynomial.value(ring: C, arg: T): T? = +fun , C : Ring> PiecewisePolynomial.value(ring: C, arg: T): T? = findPiece(arg)?.value(ring, arg) -public fun , C : Ring> PiecewisePolynomial.asFunction(ring: C): (T) -> T? = { value(ring, it) } \ No newline at end of file +fun , C : Ring> PiecewisePolynomial.asFunction(ring: C): (T) -> T? = { value(ring, it) } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Polynomial.kt new file mode 100644 index 000000000..b747b521d --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Polynomial.kt @@ -0,0 +1,73 @@ +package scientifik.kmath.functions + +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.Space +import kotlin.math.max +import kotlin.math.pow + +/** + * Polynomial coefficients without fixation on specific context they are applied to + * @param coefficients constant is the leftmost coefficient + */ +inline class Polynomial(val coefficients: List) { + constructor(vararg coefficients: T) : this(coefficients.toList()) +} + +fun Polynomial.value() = + coefficients.reduceIndexed { index: Int, acc: Double, d: Double -> acc + d.pow(index) } + + +fun > Polynomial.value(ring: C, arg: T): T = ring.run { + if (coefficients.isEmpty()) return@run zero + var res = coefficients.first() + var powerArg = arg + for (index in 1 until coefficients.size) { + res += coefficients[index] * powerArg + //recalculating power on each step to avoid power costs on long polynomials + powerArg *= arg + } + return@run res +} + +/** + * Represent a polynomial as a context-dependent function + */ +fun > Polynomial.asMathFunction(): MathFunction = object : + MathFunction { + override fun C.invoke(arg: T): T = value(this, arg) +} + +/** + * Represent the polynomial as a regular context-less function + */ +fun > Polynomial.asFunction(ring: C): (T) -> T = { value(ring, it) } + +/** + * An algebra for polynomials + */ +class PolynomialSpace>(val ring: C) : Space> { + + override fun add(a: Polynomial, b: Polynomial): Polynomial { + val dim = max(a.coefficients.size, b.coefficients.size) + ring.run { + return Polynomial(List(dim) { index -> + a.coefficients.getOrElse(index) { zero } + b.coefficients.getOrElse(index) { zero } + }) + } + } + + override fun multiply(a: Polynomial, k: Number): Polynomial { + ring.run { + return Polynomial(List(a.coefficients.size) { index -> a.coefficients[index] * k }) + } + } + + override val zero: Polynomial = + Polynomial(emptyList()) + + operator fun Polynomial.invoke(arg: T): T = value(ring, arg) +} + +fun , R> C.polynomial(block: PolynomialSpace.() -> R): R { + return PolynomialSpace(this).run(block) +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/functions.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/functions.kt new file mode 100644 index 000000000..2b822b3ba --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/functions.kt @@ -0,0 +1,33 @@ +package scientifik.kmath.functions + +import scientifik.kmath.operations.Algebra +import scientifik.kmath.operations.RealField + +/** + * A regular function that could be called only inside specific algebra context + * @param T source type + * @param C source algebra constraint + * @param R result type + */ +interface MathFunction, R> { + operator fun C.invoke(arg: T): R +} + +fun MathFunction.invoke(arg: Double): R = RealField.invoke(arg) + +/** + * A suspendable function defined in algebraic context + */ +interface SuspendableMathFunction, R> { + suspend operator fun C.invoke(arg: T): R +} + +suspend fun SuspendableMathFunction.invoke(arg: Double) = RealField.invoke(arg) + + +/** + * A parametric function with parameter + */ +interface ParametricFunction> { + operator fun C.invoke(arg: T, parameter: P): T +} diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt new file mode 100644 index 000000000..8d83e4198 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt @@ -0,0 +1,45 @@ +package scientifik.kmath.interpolation + +import scientifik.kmath.functions.PiecewisePolynomial +import scientifik.kmath.functions.value +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.asBuffer + +interface Interpolator { + fun interpolate(points: XYPointSet): (X) -> Y +} + +interface PolynomialInterpolator> : Interpolator { + val algebra: Ring + + fun getDefaultValue(): T = error("Out of bounds") + + fun interpolatePolynomials(points: XYPointSet): PiecewisePolynomial + + override fun interpolate(points: XYPointSet): (T) -> T = { x -> + interpolatePolynomials(points).value(algebra, x) ?: getDefaultValue() + } +} + +fun > PolynomialInterpolator.interpolatePolynomials( + x: Buffer, + y: Buffer +): PiecewisePolynomial { + val pointSet = BufferXYPointSet(x, y) + return interpolatePolynomials(pointSet) +} + +fun > PolynomialInterpolator.interpolatePolynomials( + data: Map +): PiecewisePolynomial { + val pointSet = BufferXYPointSet(data.keys.toList().asBuffer(), data.values.toList().asBuffer()) + return interpolatePolynomials(pointSet) +} + +fun > PolynomialInterpolator.interpolatePolynomials( + data: List> +): PiecewisePolynomial { + val pointSet = BufferXYPointSet(data.map { it.first }.asBuffer(), data.map { it.second }.asBuffer()) + return interpolatePolynomials(pointSet) +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/LinearInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt similarity index 57% rename from kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/LinearInterpolator.kt rename to kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt index 377aa1fbe..98beb4391 100644 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/LinearInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt @@ -1,16 +1,16 @@ -package kscience.kmath.interpolation +package scientifik.kmath.interpolation -import kscience.kmath.functions.OrderedPiecewisePolynomial -import kscience.kmath.functions.PiecewisePolynomial -import kscience.kmath.functions.Polynomial -import kscience.kmath.operations.Field -import kscience.kmath.operations.invoke +import scientifik.kmath.functions.OrderedPiecewisePolynomial +import scientifik.kmath.functions.PiecewisePolynomial +import scientifik.kmath.functions.Polynomial +import scientifik.kmath.operations.Field /** * Reference JVM implementation: https://github.com/apache/commons-math/blob/master/src/main/java/org/apache/commons/math4/analysis/interpolation/LinearInterpolator.java */ -public class LinearInterpolator>(public override val algebra: Field) : PolynomialInterpolator { - public override fun interpolatePolynomials(points: XYPointSet): PiecewisePolynomial = algebra { +class LinearInterpolator>(override val algebra: Field) : PolynomialInterpolator { + + override fun interpolatePolynomials(points: XYPointSet): PiecewisePolynomial = algebra.run { require(points.size > 0) { "Point array should not be empty" } insureSorted(points) @@ -23,4 +23,4 @@ public class LinearInterpolator>(public override val algebra: } } } -} +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/LoessInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt similarity index 98% rename from kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/LoessInterpolator.kt rename to kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt index 6931857b1..6707bd8bc 100644 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/LoessInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt @@ -1,8 +1,8 @@ -package kscience.kmath.interpolation +package scientifik.kmath.interpolation // -//import kscience.kmath.functions.PiecewisePolynomial -//import kscience.kmath.operations.Ring -//import kscience.kmath.structures.Buffer +//import scientifik.kmath.functions.PiecewisePolynomial +//import scientifik.kmath.operations.Ring +//import scientifik.kmath.structures.Buffer //import kotlin.math.abs //import kotlin.math.sqrt // diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/SplineInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/SplineInterpolator.kt similarity index 69% rename from kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/SplineInterpolator.kt rename to kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/SplineInterpolator.kt index 6cda45f72..e1af0c1a2 100644 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/interpolation/SplineInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/SplineInterpolator.kt @@ -1,25 +1,28 @@ -package kscience.kmath.interpolation +package scientifik.kmath.interpolation -import kscience.kmath.functions.OrderedPiecewisePolynomial -import kscience.kmath.functions.PiecewisePolynomial -import kscience.kmath.functions.Polynomial -import kscience.kmath.operations.Field -import kscience.kmath.operations.invoke -import kscience.kmath.structures.MutableBufferFactory +import scientifik.kmath.functions.OrderedPiecewisePolynomial +import scientifik.kmath.functions.PiecewisePolynomial +import scientifik.kmath.functions.Polynomial +import scientifik.kmath.operations.Field +import scientifik.kmath.structures.MutableBufferFactory /** * Generic spline interpolator. Not recommended for performance critical places, use platform-specific and type specific ones. * Based on https://github.com/apache/commons-math/blob/eb57d6d457002a0bb5336d789a3381a24599affe/src/main/java/org/apache/commons/math4/analysis/interpolation/SplineInterpolator.java */ -public class SplineInterpolator>( - public override val algebra: Field, - public val bufferFactory: MutableBufferFactory +class SplineInterpolator>( + override val algebra: Field, + val bufferFactory: MutableBufferFactory ) : PolynomialInterpolator { + //TODO possibly optimize zeroed buffers - public override fun interpolatePolynomials(points: XYPointSet): PiecewisePolynomial = algebra { - require(points.size >= 3) { "Can't use spline interpolator with less than 3 points" } + override fun interpolatePolynomials(points: XYPointSet): PiecewisePolynomial = algebra.run { + if (points.size < 3) { + error("Can't use spline interpolator with less than 3 points") + } insureSorted(points) + // Number of intervals. The number of data points is n + 1. val n = points.size - 1 // Differences between knot points @@ -30,7 +33,6 @@ public class SplineInterpolator>( for (i in 1 until n) { val g = 2.0 * (points.x[i + 1] - points.x[i - 1]) - h[i - 1] * mu[i - 1] mu[i] = h[i] / g - z[i] = (3.0 * (points.y[i + 1] * h[i - 1] - points.x[i] * (points.x[i + 1] - points.x[i - 1]) + points.y[i - 1] * h[i]) / (h[i - 1] * h[i]) - h[i - 1] * z[i - 1]) / g @@ -38,9 +40,8 @@ public class SplineInterpolator>( // cubic spline coefficients -- b is linear, c quadratic, d is cubic (original y's are constants) - OrderedPiecewisePolynomial(points.x[points.size - 1]).apply { + OrderedPiecewisePolynomial(points.x[points.size - 1]).apply { var cOld = zero - for (j in n - 1 downTo 0) { val c = z[j] - mu[j] * cOld val a = points.y[j] @@ -51,5 +52,7 @@ public class SplineInterpolator>( putLeft(points.x[j], polynomial) } } + } + } diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/XYPointSet.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/XYPointSet.kt new file mode 100644 index 000000000..d8e10b880 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/XYPointSet.kt @@ -0,0 +1,54 @@ +package scientifik.kmath.interpolation + +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.Structure2D + +interface XYPointSet { + val size: Int + val x: Buffer + val y: Buffer +} + +interface XYZPointSet : XYPointSet { + val z: Buffer +} + +internal fun > insureSorted(points: XYPointSet) { + for (i in 0 until points.size - 1) { + if (points.x[i + 1] <= points.x[i]) error("Input data is not sorted at index $i") + } +} + +class NDStructureColumn(val structure: Structure2D, val column: Int) : Buffer { + init { + require(column < structure.colNum) { "Column index is outside of structure column range" } + } + + override val size: Int get() = structure.rowNum + + override fun get(index: Int): T = structure[index, column] + + override fun iterator(): Iterator = sequence { + repeat(size) { + yield(get(it)) + } + }.iterator() +} + +class BufferXYPointSet(override val x: Buffer, override val y: Buffer) : XYPointSet { + init { + require(x.size == y.size) { "Sizes of x and y buffers should be the same" } + } + + override val size: Int + get() = x.size +} + +fun Structure2D.asXYPointSet(): XYPointSet { + require(shape[1] == 2) { "Structure second dimension should be of size 2" } + return object : XYPointSet { + override val size: Int get() = this@asXYPointSet.shape[0] + override val x: Buffer get() = NDStructureColumn(this@asXYPointSet, 0) + override val y: Buffer get() = NDStructureColumn(this@asXYPointSet, 1) + } +} \ No newline at end of file diff --git a/kmath-functions/src/commonTest/kotlin/kscience/kmath/interpolation/LinearInterpolatorTest.kt b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt similarity index 72% rename from kmath-functions/src/commonTest/kotlin/kscience/kmath/interpolation/LinearInterpolatorTest.kt rename to kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt index 303615676..23acd835c 100644 --- a/kmath-functions/src/commonTest/kotlin/kscience/kmath/interpolation/LinearInterpolatorTest.kt +++ b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt @@ -1,12 +1,13 @@ -package kscience.kmath.interpolation +package scientifik.kmath.interpolation -import kscience.kmath.functions.PiecewisePolynomial -import kscience.kmath.functions.asFunction -import kscience.kmath.operations.RealField +import scientifik.kmath.functions.PiecewisePolynomial +import scientifik.kmath.functions.asFunction +import scientifik.kmath.operations.RealField import kotlin.test.Test import kotlin.test.assertEquals -internal class LinearInterpolatorTest { + +class LinearInterpolatorTest { @Test fun testInterpolation() { val data = listOf( @@ -15,12 +16,12 @@ internal class LinearInterpolatorTest { 2.0 to 3.0, 3.0 to 4.0 ) - val polynomial: PiecewisePolynomial = LinearInterpolator(RealField).interpolatePolynomials(data) val function = polynomial.asFunction(RealField) + assertEquals(null, function(-1.0)) assertEquals(0.5, function(0.5)) assertEquals(2.0, function(1.5)) assertEquals(3.0, function(2.0)) } -} +} \ No newline at end of file diff --git a/kmath-geometry/build.gradle.kts b/kmath-geometry/build.gradle.kts index 00abcb934..39aa833ad 100644 --- a/kmath-geometry/build.gradle.kts +++ b/kmath-geometry/build.gradle.kts @@ -1,7 +1,9 @@ -plugins { id("ru.mipt.npm.mpp") } +plugins { + id("scientifik.mpp") +} kotlin.sourceSets.commonMain { dependencies { api(project(":kmath-core")) } -} +} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/Euclidean2DSpace.kt b/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/Euclidean2DSpace.kt deleted file mode 100644 index c2a883a64..000000000 --- a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/Euclidean2DSpace.kt +++ /dev/null @@ -1,47 +0,0 @@ -package kscience.kmath.geometry - -import kscience.kmath.linear.Point -import kscience.kmath.operations.SpaceElement -import kscience.kmath.operations.invoke -import kotlin.math.sqrt - -public interface Vector2D : Point, Vector, SpaceElement { - public val x: Double - public val y: Double - public override val context: Euclidean2DSpace get() = Euclidean2DSpace - public override val size: Int get() = 2 - - public override operator fun get(index: Int): Double = when (index) { - 1 -> x - 2 -> y - else -> error("Accessing outside of point bounds") - } - - public override operator fun iterator(): Iterator = listOf(x, y).iterator() - public override fun unwrap(): Vector2D = this - public override fun Vector2D.wrap(): Vector2D = this -} - -public val Vector2D.r: Double - get() = Euclidean2DSpace { sqrt(norm()) } - -@Suppress("FunctionName") -public fun Vector2D(x: Double, y: Double): Vector2D = Vector2DImpl(x, y) - -private data class Vector2DImpl( - override val x: Double, - override val y: Double -) : Vector2D - -/** - * 2D Euclidean space - */ -public object Euclidean2DSpace : GeometrySpace { - public override val zero: Vector2D by lazy { Vector2D(0.0, 0.0) } - - public fun Vector2D.norm(): Double = sqrt(x * x + y * y) - public override fun Vector2D.distanceTo(other: Vector2D): Double = (this - other).norm() - public override fun add(a: Vector2D, b: Vector2D): Vector2D = Vector2D(a.x + b.x, a.y + b.y) - public override fun multiply(a: Vector2D, k: Number): Vector2D = Vector2D(a.x * k.toDouble(), a.y * k.toDouble()) - public override fun Vector2D.dot(other: Vector2D): Double = x * other.x + y * other.y -} diff --git a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/Euclidean3DSpace.kt b/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/Euclidean3DSpace.kt deleted file mode 100644 index e0052d791..000000000 --- a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/Euclidean3DSpace.kt +++ /dev/null @@ -1,53 +0,0 @@ -package kscience.kmath.geometry - -import kscience.kmath.linear.Point -import kscience.kmath.operations.SpaceElement -import kscience.kmath.operations.invoke -import kotlin.math.sqrt - -public interface Vector3D : Point, Vector, SpaceElement { - public val x: Double - public val y: Double - public val z: Double - public override val context: Euclidean3DSpace get() = Euclidean3DSpace - public override val size: Int get() = 3 - - public override operator fun get(index: Int): Double = when (index) { - 1 -> x - 2 -> y - 3 -> z - else -> error("Accessing outside of point bounds") - } - - public override operator fun iterator(): Iterator = listOf(x, y, z).iterator() - public override fun unwrap(): Vector3D = this - public override fun Vector3D.wrap(): Vector3D = this -} - -@Suppress("FunctionName") -public fun Vector3D(x: Double, y: Double, z: Double): Vector3D = Vector3DImpl(x, y, z) - -public val Vector3D.r: Double get() = Euclidean3DSpace { sqrt(norm()) } - -private data class Vector3DImpl( - override val x: Double, - override val y: Double, - override val z: Double -) : Vector3D - -public object Euclidean3DSpace : GeometrySpace { - public override val zero: Vector3D by lazy { Vector3D(0.0, 0.0, 0.0) } - - public fun Vector3D.norm(): Double = sqrt(x * x + y * y + z * z) - - public override fun Vector3D.distanceTo(other: Vector3D): Double = (this - other).norm() - - public override fun add(a: Vector3D, b: Vector3D): Vector3D = - Vector3D(a.x + b.x, a.y + b.y, a.z + b.z) - - public override fun multiply(a: Vector3D, k: Number): Vector3D = - Vector3D(a.x * k.toDouble(), a.y * k.toDouble(), a.z * k.toDouble()) - - public override fun Vector3D.dot(other: Vector3D): Double = - x * other.x + y * other.y + z * other.z -} diff --git a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/GeometrySpace.kt b/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/GeometrySpace.kt deleted file mode 100644 index 54d2510cf..000000000 --- a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/GeometrySpace.kt +++ /dev/null @@ -1,17 +0,0 @@ -package kscience.kmath.geometry - -import kscience.kmath.operations.Space - -public interface Vector - -public interface GeometrySpace : Space { - /** - * L2 distance - */ - public fun V.distanceTo(other: V): Double - - /** - * Scalar product - */ - public infix fun V.dot(other: V): Double -} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/Line.kt b/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/Line.kt deleted file mode 100644 index ec2ce31ca..000000000 --- a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/Line.kt +++ /dev/null @@ -1,6 +0,0 @@ -package kscience.kmath.geometry - -public data class Line(val base: V, val direction: V) - -public typealias Line2D = Line -public typealias Line3D = Line diff --git a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/ReferenceFrame.kt b/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/ReferenceFrame.kt deleted file mode 100644 index f9de7b51f..000000000 --- a/kmath-geometry/src/commonMain/kotlin/kscience/kmath/geometry/ReferenceFrame.kt +++ /dev/null @@ -1,3 +0,0 @@ -package kscience.kmath.geometry - -public interface ReferenceFrame diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean2DSpace.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean2DSpace.kt new file mode 100644 index 000000000..2313b2170 --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean2DSpace.kt @@ -0,0 +1,58 @@ +package scientifik.kmath.geometry + +import scientifik.kmath.linear.Point +import scientifik.kmath.operations.SpaceElement +import scientifik.kmath.operations.invoke +import kotlin.math.sqrt + + +interface Vector2D : Point, Vector, SpaceElement { + val x: Double + val y: Double + + override val size: Int get() = 2 + + override fun get(index: Int): Double = when (index) { + 1 -> x + 2 -> y + else -> error("Accessing outside of point bounds") + } + + override fun iterator(): Iterator = listOf(x, y).iterator() + + override val context: Euclidean2DSpace get() = Euclidean2DSpace + + override fun unwrap(): Vector2D = this + + override fun Vector2D.wrap(): Vector2D = this +} + +val Vector2D.r: Double get() = Euclidean2DSpace.run { sqrt(norm()) } + +@Suppress("FunctionName") +fun Vector2D(x: Double, y: Double): Vector2D = Vector2DImpl(x, y) + +private data class Vector2DImpl( + override val x: Double, + override val y: Double +) : Vector2D + +/** + * 2D Euclidean space + */ +object Euclidean2DSpace : GeometrySpace { + fun Vector2D.norm(): Double = sqrt(x * x + y * y) + + override fun Vector2D.distanceTo(other: Vector2D): Double = (this - other).norm() + + override fun add(a: Vector2D, b: Vector2D): Vector2D = + Vector2D(a.x + b.x, a.y + b.y) + + override fun multiply(a: Vector2D, k: Number): Vector2D = + Vector2D(a.x * k.toDouble(), a.y * k.toDouble()) + + override val zero: Vector2D = Vector2D(0.0, 0.0) + + override fun Vector2D.dot(other: Vector2D): Double = + x * other.x + y * other.y +} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean3DSpace.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean3DSpace.kt new file mode 100644 index 000000000..dd1776342 --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean3DSpace.kt @@ -0,0 +1,57 @@ +package scientifik.kmath.geometry + +import scientifik.kmath.linear.Point +import scientifik.kmath.operations.SpaceElement +import kotlin.math.sqrt + + +interface Vector3D : Point, Vector, SpaceElement { + val x: Double + val y: Double + val z: Double + + override val size: Int get() = 3 + + override fun get(index: Int): Double = when (index) { + 1 -> x + 2 -> y + 3 -> z + else -> error("Accessing outside of point bounds") + } + + override fun iterator(): Iterator = listOf(x, y, z).iterator() + + override val context: Euclidean3DSpace get() = Euclidean3DSpace + + override fun unwrap(): Vector3D = this + + override fun Vector3D.wrap(): Vector3D = this +} + +@Suppress("FunctionName") +fun Vector3D(x: Double, y: Double, z: Double): Vector3D = Vector3DImpl(x, y, z) + +val Vector3D.r: Double get() = Euclidean3DSpace.run { sqrt(norm()) } + +private data class Vector3DImpl( + override val x: Double, + override val y: Double, + override val z: Double +) : Vector3D + +object Euclidean3DSpace : GeometrySpace { + override val zero: Vector3D = Vector3D(0.0, 0.0, 0.0) + + fun Vector3D.norm(): Double = sqrt(x * x + y * y + z * z) + + override fun Vector3D.distanceTo(other: Vector3D): Double = (this - other).norm() + + override fun add(a: Vector3D, b: Vector3D): Vector3D = + Vector3D(a.x + b.x, a.y + b.y, a.z + b.z) + + override fun multiply(a: Vector3D, k: Number): Vector3D = + Vector3D(a.x * k.toDouble(), a.y * k.toDouble(), a.z * k.toDouble()) + + override fun Vector3D.dot(other: Vector3D): Double = + x * other.x + y * other.y + z * other.z +} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/GeometrySpace.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/GeometrySpace.kt new file mode 100644 index 000000000..b65a8dd3a --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/GeometrySpace.kt @@ -0,0 +1,17 @@ +package scientifik.kmath.geometry + +import scientifik.kmath.operations.Space + +interface Vector + +interface GeometrySpace: Space { + /** + * L2 distance + */ + fun V.distanceTo(other: V): Double + + /** + * Scalar product + */ + infix fun V.dot(other: V): Double +} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Line.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Line.kt new file mode 100644 index 000000000..d802a103f --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Line.kt @@ -0,0 +1,6 @@ +package scientifik.kmath.geometry + +data class Line(val base: V, val direction: V) + +typealias Line2D = Line +typealias Line3D = Line diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/ReferenceFrame.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/ReferenceFrame.kt new file mode 100644 index 000000000..420e38ce2 --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/ReferenceFrame.kt @@ -0,0 +1,4 @@ +package scientifik.kmath.geometry + +interface ReferenceFrame { +} \ No newline at end of file diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 7de21ad89..993bfed8e 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -1,8 +1,10 @@ -plugins { id("ru.mipt.npm.mpp") } +plugins { + id("scientifik.mpp") +} kotlin.sourceSets.commonMain { dependencies { api(project(":kmath-core")) api(project(":kmath-for-real")) } -} +} \ No newline at end of file diff --git a/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/Counters.kt b/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/Counters.kt deleted file mode 100644 index 7a263a9fc..000000000 --- a/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/Counters.kt +++ /dev/null @@ -1,20 +0,0 @@ -package kscience.kmath.histogram - -/* - * Common representation for atomic counters - * TODO replace with atomics - */ - -public expect class LongCounter() { - public fun decrement() - public fun increment() - public fun reset() - public fun sum(): Long - public fun add(l: Long) -} - -public expect class DoubleCounter() { - public fun reset() - public fun sum(): Double - public fun add(d: Double) -} diff --git a/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/Histogram.kt b/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/Histogram.kt deleted file mode 100644 index 370a01215..000000000 --- a/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/Histogram.kt +++ /dev/null @@ -1,54 +0,0 @@ -package kscience.kmath.histogram - -import kscience.kmath.domains.Domain -import kscience.kmath.linear.Point -import kscience.kmath.structures.ArrayBuffer -import kscience.kmath.structures.RealBuffer - -/** - * The bin in the histogram. The histogram is by definition always done in the real space - */ -public interface Bin : Domain { - /** - * The value of this bin. - */ - public val value: Number - - public val center: Point -} - -public interface Histogram> : Iterable { - /** - * Find existing bin, corresponding to given coordinates - */ - public operator fun get(point: Point): B? - - /** - * Dimension of the histogram - */ - public val dimension: Int -} - -public interface MutableHistogram> : Histogram { - - /** - * Increment appropriate bin - */ - public fun putWithWeight(point: Point, weight: Double) - - public fun put(point: Point): Unit = putWithWeight(point, 1.0) -} - -public fun MutableHistogram.put(vararg point: T): Unit = put(ArrayBuffer(point)) - -public fun MutableHistogram.put(vararg point: Number): Unit = - put(RealBuffer(point.map { it.toDouble() }.toDoubleArray())) - -public fun MutableHistogram.put(vararg point: Double): Unit = put(RealBuffer(point)) -public fun MutableHistogram.fill(sequence: Iterable>): Unit = sequence.forEach { put(it) } - -/** - * Pass a sequence builder into histogram - */ -public fun MutableHistogram.fill(block: suspend SequenceScope>.() -> Unit): Unit = - fill(sequence(block).asIterable()) diff --git a/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/RealHistogram.kt b/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/RealHistogram.kt deleted file mode 100644 index f95264ee1..000000000 --- a/kmath-histograms/src/commonMain/kotlin/kscience/kmath/histogram/RealHistogram.kt +++ /dev/null @@ -1,155 +0,0 @@ -package kscience.kmath.histogram - -import kscience.kmath.linear.Point -import kscience.kmath.operations.SpaceOperations -import kscience.kmath.operations.invoke -import kscience.kmath.structures.* -import kotlin.math.floor - -public data class BinDef>( - public val space: SpaceOperations>, - public val center: Point, - public val sizes: Point -) { - public fun contains(vector: Point): Boolean { - require(vector.size == center.size) { "Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}" } - val upper = space { center + sizes / 2.0 } - val lower = space { center - sizes / 2.0 } - return vector.asSequence().mapIndexed { i, value -> value in lower[i]..upper[i] }.all { it } - } -} - - -public class MultivariateBin>(public val def: BinDef, public override val value: Number) : Bin { - public override val dimension: Int - get() = def.center.size - - public override val center: Point - get() = def.center - - public override operator fun contains(point: Point): Boolean = def.contains(point) -} - -/** - * Uniform multivariate histogram with fixed borders. Based on NDStructure implementation with complexity of m for bin search, where m is the number of dimensions. - */ -public class RealHistogram( - private val lower: Buffer, - private val upper: Buffer, - private val binNums: IntArray = IntArray(lower.size) { 20 } -) : MutableHistogram> { - private val strides = DefaultStrides(IntArray(binNums.size) { binNums[it] + 2 }) - private val values: NDStructure = NDStructure.auto(strides) { LongCounter() } - private val weights: NDStructure = NDStructure.auto(strides) { DoubleCounter() } - public override val dimension: Int get() = lower.size - private val binSize = RealBuffer(dimension) { (upper[it] - lower[it]) / binNums[it] } - - init { - // argument checks - require(lower.size == upper.size) { "Dimension mismatch in histogram lower and upper limits." } - require(lower.size == binNums.size) { "Dimension mismatch in bin count." } - require(!(0 until dimension).any { upper[it] - lower[it] < 0 }) { "Range for one of axis is not strictly positive" } - } - - - /** - * Get internal [NDStructure] bin index for given axis - */ - private fun getIndex(axis: Int, value: Double): Int = when { - value >= upper[axis] -> binNums[axis] + 1 // overflow - value < lower[axis] -> 0 // underflow - else -> floor((value - lower[axis]) / binSize[axis]).toInt() + 1 - } - - private fun getIndex(point: Buffer): IntArray = IntArray(dimension) { getIndex(it, point[it]) } - - private fun getValue(index: IntArray): Long = values[index].sum() - - public fun getValue(point: Buffer): Long = getValue(getIndex(point)) - - private fun getDef(index: IntArray): BinDef { - val center = index.mapIndexed { axis, i -> - when (i) { - 0 -> Double.NEGATIVE_INFINITY - strides.shape[axis] - 1 -> Double.POSITIVE_INFINITY - else -> lower[axis] + (i.toDouble() - 0.5) * binSize[axis] - } - }.asBuffer() - - return BinDef(RealBufferFieldOperations, center, binSize) - } - - public fun getDef(point: Buffer): BinDef = getDef(getIndex(point)) - - public override operator fun get(point: Buffer): MultivariateBin? { - val index = getIndex(point) - return MultivariateBin(getDef(index), getValue(index)) - } - -// fun put(point: Point){ -// val index = getIndex(point) -// values[index].increment() -// } - - public override fun putWithWeight(point: Buffer, weight: Double) { - val index = getIndex(point) - values[index].increment() - weights[index].add(weight) - } - - public override operator fun iterator(): Iterator> = - weights.elements().map { (index, value) -> MultivariateBin(getDef(index), value.sum()) } - .iterator() - - /** - * Convert this histogram into NDStructure containing bin values but not bin descriptions - */ - public fun values(): NDStructure = NDStructure.auto(values.shape) { values[it].sum() } - - /** - * Sum of weights - */ - public fun weights(): NDStructure = NDStructure.auto(weights.shape) { weights[it].sum() } - - public companion object { - /** - * Use it like - * ``` - *FastHistogram.fromRanges( - * (-1.0..1.0), - * (-1.0..1.0) - *) - *``` - */ - public fun fromRanges(vararg ranges: ClosedFloatingPointRange): RealHistogram = RealHistogram( - ranges.map(ClosedFloatingPointRange::start).asBuffer(), - ranges.map(ClosedFloatingPointRange::endInclusive).asBuffer() - ) - - /** - * Use it like - * ``` - *FastHistogram.fromRanges( - * (-1.0..1.0) to 50, - * (-1.0..1.0) to 32 - *) - *``` - */ - public fun fromRanges(vararg ranges: Pair, Int>): RealHistogram = - RealHistogram( - ListBuffer( - ranges - .map(Pair, Int>::first) - .map(ClosedFloatingPointRange::start) - ), - - ListBuffer( - ranges - .map(Pair, Int>::first) - .map(ClosedFloatingPointRange::endInclusive) - ), - - ranges.map(Pair, Int>::second).toIntArray() - ) - } -} diff --git a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt new file mode 100644 index 000000000..9c7de3303 --- /dev/null +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -0,0 +1,20 @@ +package scientifik.kmath.histogram + +/* + * Common representation for atomic counters + * TODO replace with atomics + */ + +expect class LongCounter() { + fun decrement() + fun increment() + fun reset() + fun sum(): Long + fun add(l: Long) +} + +expect class DoubleCounter() { + fun reset() + fun sum(): Double + fun add(d: Double) +} \ No newline at end of file diff --git a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt new file mode 100644 index 000000000..43d50ad20 --- /dev/null +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt @@ -0,0 +1,56 @@ +package scientifik.kmath.histogram + +import scientifik.kmath.domains.Domain +import scientifik.kmath.linear.Point +import scientifik.kmath.structures.ArrayBuffer +import scientifik.kmath.structures.RealBuffer + +/** + * The bin in the histogram. The histogram is by definition always done in the real space + */ +interface Bin : Domain { + /** + * The value of this bin + */ + val value: Number + val center: Point +} + +interface Histogram> : Iterable { + + /** + * Find existing bin, corresponding to given coordinates + */ + operator fun get(point: Point): B? + + /** + * Dimension of the histogram + */ + val dimension: Int + +} + +interface MutableHistogram> : Histogram { + + /** + * Increment appropriate bin + */ + fun putWithWeight(point: Point, weight: Double) + + fun put(point: Point) = putWithWeight(point, 1.0) +} + +fun MutableHistogram.put(vararg point: T) = put(ArrayBuffer(point)) + +fun MutableHistogram.put(vararg point: Number) = + put(RealBuffer(point.map { it.toDouble() }.toDoubleArray())) + +fun MutableHistogram.put(vararg point: Double) = put(RealBuffer(point)) + +fun MutableHistogram.fill(sequence: Iterable>) = sequence.forEach { put(it) } + +/** + * Pass a sequence builder into histogram + */ +fun MutableHistogram.fill(buider: suspend SequenceScope>.() -> Unit) = + fill(sequence(buider).asIterable()) \ No newline at end of file diff --git a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt new file mode 100644 index 000000000..628a68461 --- /dev/null +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt @@ -0,0 +1,169 @@ +package scientifik.kmath.histogram + +import scientifik.kmath.linear.Point +import scientifik.kmath.operations.SpaceOperations +import scientifik.kmath.real.asVector +import scientifik.kmath.structures.* +import kotlin.math.floor + + +data class BinDef>(val space: SpaceOperations>, val center: Point, val sizes: Point) { + fun contains(vector: Point): Boolean { + if (vector.size != center.size) error("Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}") + val upper = space.run { center + sizes / 2.0 } + val lower = space.run { center - sizes / 2.0 } + return vector.asSequence().mapIndexed { i, value -> + value in lower[i]..upper[i] + }.all { it } + } +} + + +class MultivariateBin>(val def: BinDef, override val value: Number) : Bin { + + override fun contains(point: Point): Boolean = def.contains(point) + + override val dimension: Int + get() = def.center.size + + override val center: Point + get() = def.center + +} + +/** + * Uniform multivariate histogram with fixed borders. Based on NDStructure implementation with complexity of m for bin search, where m is the number of dimensions. + */ +class RealHistogram( + private val lower: Buffer, + private val upper: Buffer, + private val binNums: IntArray = IntArray(lower.size) { 20 } +) : MutableHistogram> { + + + private val strides = DefaultStrides(IntArray(binNums.size) { binNums[it] + 2 }) + + private val values: NDStructure = NDStructure.auto(strides) { LongCounter() } + + private val weights: NDStructure = NDStructure.auto(strides) { DoubleCounter() } + + override val dimension: Int get() = lower.size + + + private val binSize = RealBuffer(dimension) { (upper[it] - lower[it]) / binNums[it] } + + init { + // argument checks + if (lower.size != upper.size) error("Dimension mismatch in histogram lower and upper limits.") + if (lower.size != binNums.size) error("Dimension mismatch in bin count.") + if ((0 until dimension).any { upper[it] - lower[it] < 0 }) error("Range for one of axis is not strictly positive") + } + + + /** + * Get internal [NDStructure] bin index for given axis + */ + private fun getIndex(axis: Int, value: Double): Int { + return when { + value >= upper[axis] -> binNums[axis] + 1 // overflow + value < lower[axis] -> 0 // underflow + else -> floor((value - lower[axis]) / binSize[axis]).toInt() + 1 + } + } + + private fun getIndex(point: Buffer): IntArray = IntArray(dimension) { getIndex(it, point[it]) } + + private fun getValue(index: IntArray): Long { + return values[index].sum() + } + + fun getValue(point: Buffer): Long { + return getValue(getIndex(point)) + } + + private fun getDef(index: IntArray): BinDef { + val center = index.mapIndexed { axis, i -> + when (i) { + 0 -> Double.NEGATIVE_INFINITY + strides.shape[axis] - 1 -> Double.POSITIVE_INFINITY + else -> lower[axis] + (i.toDouble() - 0.5) * binSize[axis] + } + }.asBuffer() + return BinDef(RealBufferFieldOperations, center, binSize) + } + + fun getDef(point: Buffer): BinDef { + return getDef(getIndex(point)) + } + + override fun get(point: Buffer): MultivariateBin? { + val index = getIndex(point) + return MultivariateBin(getDef(index), getValue(index)) + } + +// fun put(point: Point){ +// val index = getIndex(point) +// values[index].increment() +// } + + override fun putWithWeight(point: Buffer, weight: Double) { + val index = getIndex(point) + values[index].increment() + weights[index].add(weight) + } + + override fun iterator(): Iterator> = weights.elements().map { (index, value) -> + MultivariateBin(getDef(index), value.sum()) + }.iterator() + + /** + * Convert this histogram into NDStructure containing bin values but not bin descriptions + */ + fun values(): NDStructure { + return NDStructure.auto(values.shape) { values[it].sum() } + } + + /** + * Sum of weights + */ + fun weights():NDStructure{ + return NDStructure.auto(weights.shape) { weights[it].sum() } + } + + companion object { + + /** + * Use it like + * ``` + *FastHistogram.fromRanges( + * (-1.0..1.0), + * (-1.0..1.0) + *) + *``` + */ + fun fromRanges(vararg ranges: ClosedFloatingPointRange): RealHistogram { + return RealHistogram( + ranges.map { it.start }.asVector(), + ranges.map { it.endInclusive }.asVector() + ) + } + + /** + * Use it like + * ``` + *FastHistogram.fromRanges( + * (-1.0..1.0) to 50, + * (-1.0..1.0) to 32 + *) + *``` + */ + fun fromRanges(vararg ranges: Pair, Int>): RealHistogram { + return RealHistogram( + ListBuffer(ranges.map { it.first.start }), + ListBuffer(ranges.map { it.first.endInclusive }), + ranges.map { it.second }.toIntArray() + ) + } + } + +} \ No newline at end of file diff --git a/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt index af22afc6b..5edecb5a5 100644 --- a/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt +++ b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt @@ -1,15 +1,16 @@ package scietifik.kmath.histogram -import kscience.kmath.histogram.RealHistogram -import kscience.kmath.histogram.fill -import kscience.kmath.histogram.put -import kscience.kmath.real.RealVector -import kscience.kmath.real.invoke -import kscience.kmath.structures.Buffer +import scientifik.kmath.histogram.RealHistogram +import scientifik.kmath.histogram.fill +import scientifik.kmath.histogram.put +import scientifik.kmath.real.RealVector import kotlin.random.Random -import kotlin.test.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue -internal class MultivariateHistogramTest { +class MultivariateHistogramTest { @Test fun testSinglePutHistogram() { val histogram = RealHistogram.fromRanges( @@ -17,7 +18,7 @@ internal class MultivariateHistogramTest { (-1.0..1.0) ) histogram.put(0.55, 0.55) - val bin = histogram.find { it.value.toInt() > 0 } ?: fail() + val bin = histogram.find { it.value.toInt() > 0 }!! assertTrue { bin.contains(RealVector(0.55, 0.55)) } assertTrue { bin.contains(RealVector(0.6, 0.5)) } assertFalse { bin.contains(RealVector(-0.55, 0.55)) } diff --git a/kmath-histograms/src/jsMain/kotlin/kscience/kmath/histogram/Counters.kt b/kmath-histograms/src/jsMain/kotlin/kscience/kmath/histogram/Counters.kt deleted file mode 100644 index d0fa1f4c2..000000000 --- a/kmath-histograms/src/jsMain/kotlin/kscience/kmath/histogram/Counters.kt +++ /dev/null @@ -1,37 +0,0 @@ -package kscience.kmath.histogram - -public actual class LongCounter { - private var sum: Long = 0L - - public actual fun decrement() { - sum-- - } - - public actual fun increment() { - sum++ - } - - public actual fun reset() { - sum = 0 - } - - public actual fun sum(): Long = sum - - public actual fun add(l: Long) { - sum += l - } -} - -public actual class DoubleCounter { - private var sum: Double = 0.0 - - public actual fun reset() { - sum = 0.0 - } - - public actual fun sum(): Double = sum - - public actual fun add(d: Double) { - sum += d - } -} diff --git a/kmath-histograms/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-histograms/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt new file mode 100644 index 000000000..3765220b9 --- /dev/null +++ b/kmath-histograms/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -0,0 +1,33 @@ +package scientifik.kmath.histogram + +actual class LongCounter { + private var sum: Long = 0 + actual fun decrement() { + sum-- + } + + actual fun increment() { + sum++ + } + + actual fun reset() { + sum = 0 + } + + actual fun sum(): Long = sum + actual fun add(l: Long) { + sum += l + } +} + +actual class DoubleCounter { + private var sum: Double = 0.0 + actual fun reset() { + sum = 0.0 + } + + actual fun sum(): Double = sum + actual fun add(d: Double) { + sum += d + } +} \ No newline at end of file diff --git a/kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/Counters.kt b/kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/Counters.kt deleted file mode 100644 index efbd185ef..000000000 --- a/kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/Counters.kt +++ /dev/null @@ -1,7 +0,0 @@ -package kscience.kmath.histogram - -import java.util.concurrent.atomic.DoubleAdder -import java.util.concurrent.atomic.LongAdder - -public actual typealias LongCounter = LongAdder -public actual typealias DoubleCounter = DoubleAdder diff --git a/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt new file mode 100644 index 000000000..bb3667f7d --- /dev/null +++ b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -0,0 +1,7 @@ +package scientifik.kmath.histogram + +import java.util.concurrent.atomic.DoubleAdder +import java.util.concurrent.atomic.LongAdder + +actual typealias LongCounter = LongAdder +actual typealias DoubleCounter = DoubleAdder \ No newline at end of file diff --git a/kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/UnivariateHistogram.kt b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt similarity index 51% rename from kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/UnivariateHistogram.kt rename to kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt index 2f3855892..af01205bf 100644 --- a/kmath-histograms/src/jvmMain/kotlin/kscience/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -1,33 +1,32 @@ -package kscience.kmath.histogram +package scientifik.kmath.histogram -import kscience.kmath.real.RealVector -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.asBuffer +import scientifik.kmath.real.RealVector +import scientifik.kmath.real.asVector +import scientifik.kmath.structures.Buffer import java.util.* import kotlin.math.floor //TODO move to common -public class UnivariateBin( - public val position: Double, - public val size: Double, - public val counter: LongCounter = LongCounter() -) : Bin { +class UnivariateBin(val position: Double, val size: Double, val counter: LongCounter = LongCounter()) : Bin { //TODO add weighting - public override val value: Number get() = counter.sum() + override val value: Number get() = counter.sum() - public override val center: RealVector get() = doubleArrayOf(position).asBuffer() - public override val dimension: Int get() = 1 + override val center: RealVector get() = doubleArrayOf(position).asVector() - public operator fun contains(value: Double): Boolean = value in (position - size / 2)..(position + size / 2) - public override fun contains(point: Buffer): Boolean = contains(point[0]) - internal operator fun inc(): UnivariateBin = this.also { counter.increment() } + operator fun contains(value: Double): Boolean = value in (position - size / 2)..(position + size / 2) + + override fun contains(point: Buffer): Boolean = contains(point[0]) + + internal operator fun inc() = this.also { counter.increment() } + + override val dimension: Int get() = 1 } /** * Univariate histogram with log(n) bin search speed */ -public class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : +class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : MutableHistogram { private val bins: TreeMap = TreeMap() @@ -44,19 +43,19 @@ public class UnivariateHistogram private constructor(private val factory: (Doubl } private fun createBin(value: Double): UnivariateBin = factory(value).also { - synchronized(this) { bins[it.position] = it } + synchronized(this) { bins.put(it.position, it) } } - public override operator fun get(point: Buffer): UnivariateBin? = get(point[0]) + override fun get(point: Buffer): UnivariateBin? = get(point[0]) - public override val dimension: Int get() = 1 + override val dimension: Int get() = 1 - public override operator fun iterator(): Iterator = bins.values.iterator() + override fun iterator(): Iterator = bins.values.iterator() /** * Thread safe put operation */ - public fun put(value: Double) { + fun put(value: Double) { (get(value) ?: createBin(value)).inc() } @@ -65,29 +64,28 @@ public class UnivariateHistogram private constructor(private val factory: (Doubl put(point[0]) } - public companion object { - public fun uniform(binSize: Double, start: Double = 0.0): UnivariateHistogram = UnivariateHistogram { value -> - val center = start + binSize * floor((value - start) / binSize + 0.5) - UnivariateBin(center, binSize) + companion object { + fun uniform(binSize: Double, start: Double = 0.0): UnivariateHistogram { + return UnivariateHistogram { value -> + val center = start + binSize * floor((value - start) / binSize + 0.5) + UnivariateBin(center, binSize) + } } - public fun custom(borders: DoubleArray): UnivariateHistogram { + fun custom(borders: DoubleArray): UnivariateHistogram { val sorted = borders.sortedArray() - return UnivariateHistogram { value -> when { value < sorted.first() -> UnivariateBin( Double.NEGATIVE_INFINITY, Double.MAX_VALUE ) - value > sorted.last() -> UnivariateBin( Double.POSITIVE_INFINITY, Double.MAX_VALUE ) - else -> { - val index = sorted.indices.first { value > sorted[it] } + val index = (0 until sorted.size).first { value > sorted[it] } val left = sorted[index] val right = sorted[index + 1] UnivariateBin((left + right) / 2, (right - left)) @@ -98,4 +96,4 @@ public class UnivariateHistogram private constructor(private val factory: (Doubl } } -public fun UnivariateHistogram.fill(sequence: Iterable): Unit = sequence.forEach(::put) +fun UnivariateHistogram.fill(sequence: Iterable) = sequence.forEach { put(it) } diff --git a/kmath-koma/build.gradle.kts b/kmath-koma/build.gradle.kts new file mode 100644 index 000000000..26955bca7 --- /dev/null +++ b/kmath-koma/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + id("scientifik.mpp") +} + +repositories { + maven("http://dl.bintray.com/kyonifer/maven") +} + +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-core")) + api("com.kyonifer:koma-core-api-common:0.12") + } + } + jvmMain { + dependencies { + api("com.kyonifer:koma-core-api-jvm:0.12") + } + } + jvmTest { + dependencies { + implementation("com.kyonifer:koma-core-ejml:0.12") + } + } + jsMain { + dependencies { + api("com.kyonifer:koma-core-api-js:0.12") + } + } +} diff --git a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt new file mode 100644 index 000000000..10deabd73 --- /dev/null +++ b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt @@ -0,0 +1,114 @@ +package scientifik.kmath.linear + +import koma.extensions.fill +import koma.matrix.MatrixFactory +import scientifik.kmath.operations.Space +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.NDStructure + +class KomaMatrixContext( + private val factory: MatrixFactory>, + private val space: Space +) : + MatrixContext { + + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T) = + KomaMatrix(factory.zeros(rows, columns).fill(initializer)) + + fun Matrix.toKoma(): KomaMatrix = if (this is KomaMatrix) { + this + } else { + produce(rowNum, colNum) { i, j -> get(i, j) } + } + + fun Point.toKoma(): KomaVector = if (this is KomaVector) { + this + } else { + KomaVector(factory.zeros(size, 1).fill { i, _ -> get(i) }) + } + + + override fun Matrix.dot(other: Matrix) = + KomaMatrix(this.toKoma().origin * other.toKoma().origin) + + override fun Matrix.dot(vector: Point) = + KomaVector(this.toKoma().origin * vector.toKoma().origin) + + override fun Matrix.unaryMinus() = + KomaMatrix(this.toKoma().origin.unaryMinus()) + + override fun add(a: Matrix, b: Matrix) = + KomaMatrix(a.toKoma().origin + b.toKoma().origin) + + override fun Matrix.minus(b: Matrix) = + KomaMatrix(this.toKoma().origin - b.toKoma().origin) + + override fun multiply(a: Matrix, k: Number): Matrix = + produce(a.rowNum, a.colNum) { i, j -> space.run { a[i, j] * k } } + + override fun Matrix.times(value: T) = + KomaMatrix(this.toKoma().origin * value) + + companion object { + + } + +} + +fun KomaMatrixContext.solve(a: Matrix, b: Matrix) = + KomaMatrix(a.toKoma().origin.solve(b.toKoma().origin)) + +fun KomaMatrixContext.solve(a: Matrix, b: Point) = + KomaVector(a.toKoma().origin.solve(b.toKoma().origin)) + +fun KomaMatrixContext.inverse(a: Matrix) = + KomaMatrix(a.toKoma().origin.inv()) + +class KomaMatrix(val origin: koma.matrix.Matrix, features: Set? = null) : FeaturedMatrix { + override val rowNum: Int get() = origin.numRows() + override val colNum: Int get() = origin.numCols() + + override val shape: IntArray get() = intArrayOf(origin.numRows(), origin.numCols()) + + override val features: Set = features ?: setOf( + object : DeterminantFeature { + override val determinant: T get() = origin.det() + }, + object : LUPDecompositionFeature { + private val lup by lazy { origin.LU() } + override val l: FeaturedMatrix get() = KomaMatrix(lup.second) + override val u: FeaturedMatrix get() = KomaMatrix(lup.third) + override val p: FeaturedMatrix get() = KomaMatrix(lup.first) + } + ) + + override fun suggestFeature(vararg features: MatrixFeature): FeaturedMatrix = + KomaMatrix(this.origin, this.features + features) + + override fun get(i: Int, j: Int): T = origin.getGeneric(i, j) + + override fun equals(other: Any?): Boolean { + return NDStructure.equals(this, other as? NDStructure<*> ?: return false) + } + + override fun hashCode(): Int { + var result = origin.hashCode() + result = 31 * result + features.hashCode() + return result + } + + +} + +class KomaVector internal constructor(val origin: koma.matrix.Matrix) : Point { + init { + if (origin.numCols() != 1) error("Only single column matrices are allowed") + } + + override val size: Int get() = origin.numRows() + + override fun get(index: Int): T = origin.getGeneric(index) + + override fun iterator(): Iterator = origin.toIterable().iterator() +} + diff --git a/kmath-kotlingrad/build.gradle.kts b/kmath-kotlingrad/build.gradle.kts deleted file mode 100644 index 3925a744c..000000000 --- a/kmath-kotlingrad/build.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -plugins { - id("ru.mipt.npm.jvm") -} - -dependencies { - implementation("com.github.breandan:kaliningraph:0.1.4") - implementation("com.github.breandan:kotlingrad:0.4.0") - api(project(":kmath-ast")) -} diff --git a/kmath-kotlingrad/src/main/kotlin/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt b/kmath-kotlingrad/src/main/kotlin/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt deleted file mode 100644 index abde9e54d..000000000 --- a/kmath-kotlingrad/src/main/kotlin/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt +++ /dev/null @@ -1,53 +0,0 @@ -package kscience.kmath.kotlingrad - -import edu.umontreal.kotlingrad.api.SFun -import kscience.kmath.ast.MST -import kscience.kmath.ast.MstAlgebra -import kscience.kmath.ast.MstExpression -import kscience.kmath.expressions.DifferentiableExpression -import kscience.kmath.expressions.Symbol -import kscience.kmath.operations.NumericAlgebra - -/** - * Represents wrapper of [MstExpression] implementing [DifferentiableExpression]. - * - * The principle of this API is converting the [mst] to an [SFun], differentiating it with Kotlin∇, then converting - * [SFun] back to [MST]. - * - * @param T the type of number. - * @param A the [NumericAlgebra] of [T]. - * @property expr the underlying [MstExpression]. - */ -public inline class DifferentiableMstExpression(public val expr: MstExpression) : - DifferentiableExpression> where A : NumericAlgebra, T : Number { - public constructor(algebra: A, mst: MST) : this(MstExpression(algebra, mst)) - - /** - * The [MstExpression.algebra] of [expr]. - */ - public val algebra: A - get() = expr.algebra - - /** - * The [MstExpression.mst] of [expr]. - */ - public val mst: MST - get() = expr.mst - - public override fun invoke(arguments: Map): T = expr(arguments) - - public override fun derivativeOrNull(symbols: List): MstExpression = MstExpression( - algebra, - symbols.map(Symbol::identity) - .map(MstAlgebra::symbol) - .map { it.toSVar>() } - .fold(mst.toSFun(), SFun>::d) - .toMst(), - ) -} - -/** - * Wraps this [MstExpression] into [DifferentiableMstExpression]. - */ -public fun > MstExpression.differentiable(): DifferentiableMstExpression = - DifferentiableMstExpression(this) diff --git a/kmath-kotlingrad/src/main/kotlin/kscience/kmath/kotlingrad/KMathNumber.kt b/kmath-kotlingrad/src/main/kotlin/kscience/kmath/kotlingrad/KMathNumber.kt deleted file mode 100644 index 2a4db4258..000000000 --- a/kmath-kotlingrad/src/main/kotlin/kscience/kmath/kotlingrad/KMathNumber.kt +++ /dev/null @@ -1,18 +0,0 @@ -package kscience.kmath.kotlingrad - -import edu.umontreal.kotlingrad.api.RealNumber -import edu.umontreal.kotlingrad.api.SConst -import kscience.kmath.operations.NumericAlgebra - -/** - * Implements [RealNumber] by delegating its functionality to [NumericAlgebra]. - * - * @param T the type of number. - * @param A the [NumericAlgebra] of [T]. - * @property algebra the algebra. - * @param value the value of this number. - */ -public class KMathNumber(public val algebra: A, value: T) : - RealNumber, T>(value) where T : Number, A : NumericAlgebra { - public override fun wrap(number: Number): SConst> = SConst(algebra.number(number)) -} diff --git a/kmath-kotlingrad/src/main/kotlin/kscience/kmath/kotlingrad/ScalarsAdapters.kt b/kmath-kotlingrad/src/main/kotlin/kscience/kmath/kotlingrad/ScalarsAdapters.kt deleted file mode 100644 index 8dc1d3958..000000000 --- a/kmath-kotlingrad/src/main/kotlin/kscience/kmath/kotlingrad/ScalarsAdapters.kt +++ /dev/null @@ -1,124 +0,0 @@ -package kscience.kmath.kotlingrad - -import edu.umontreal.kotlingrad.api.* -import kscience.kmath.ast.MST -import kscience.kmath.ast.MstAlgebra -import kscience.kmath.ast.MstExtendedField -import kscience.kmath.ast.MstExtendedField.unaryMinus -import kscience.kmath.operations.* - -/** - * Maps [SVar] to [MST.Symbolic] directly. - * - * @receiver the variable. - * @return a node. - */ -public fun > SVar.toMst(): MST.Symbolic = MstAlgebra.symbol(name) - -/** - * Maps [SVar] to [MST.Numeric] directly. - * - * @receiver the constant. - * @return a node. - */ -public fun > SConst.toMst(): MST.Numeric = MstAlgebra.number(doubleValue) - -/** - * Maps [SFun] objects to [MST]. Some unsupported operations like [Derivative] are bound and converted then. - * [Power] operation is limited to constant right-hand side arguments. - * - * Detailed mapping is: - * - * - [SVar] -> [MstExtendedField.symbol]; - * - [SConst] -> [MstExtendedField.number]; - * - [Sum] -> [MstExtendedField.add]; - * - [Prod] -> [MstExtendedField.multiply]; - * - [Power] -> [MstExtendedField.power] (limited to constant exponents only); - * - [Negative] -> [MstExtendedField.unaryMinus]; - * - [Log] -> [MstExtendedField.ln] (left) / [MstExtendedField.ln] (right); - * - [Sine] -> [MstExtendedField.sin]; - * - [Cosine] -> [MstExtendedField.cos]; - * - [Tangent] -> [MstExtendedField.tan]; - * - [DProd] is vector operation, and it is requested to be evaluated; - * - [SComposition] is also requested to be evaluated eagerly; - * - [VSumAll] is requested to be evaluated; - * - [Derivative] is requested to be evaluated. - * - * @receiver the scalar function. - * @return a node. - */ -public fun > SFun.toMst(): MST = MstExtendedField { - when (this@toMst) { - is SVar -> toMst() - is SConst -> toMst() - is Sum -> left.toMst() + right.toMst() - is Prod -> left.toMst() * right.toMst() - is Power -> left.toMst() pow ((right as? SConst<*>)?.doubleValue ?: (right() as SConst<*>).doubleValue) - is Negative -> -input.toMst() - is Log -> ln(left.toMst()) / ln(right.toMst()) - is Sine -> sin(input.toMst()) - is Cosine -> cos(input.toMst()) - is Tangent -> tan(input.toMst()) - is DProd -> this@toMst().toMst() - is SComposition -> this@toMst().toMst() - is VSumAll -> this@toMst().toMst() - is Derivative -> this@toMst().toMst() - } -} - -/** - * Maps [MST.Numeric] to [SConst] directly. - * - * @receiver the node. - * @return a new constant. - */ -public fun > MST.Numeric.toSConst(): SConst = SConst(value) - -/** - * Maps [MST.Symbolic] to [SVar] directly. - * - * @receiver the node. - * @param proto the prototype instance. - * @return a new variable. - */ -internal fun > MST.Symbolic.toSVar(): SVar = SVar(value) - -/** - * Maps [MST] objects to [SFun]. Unsupported operations throw [IllegalStateException]. - * - * Detailed mapping is: - * - * - [MST.Numeric] -> [SConst]; - * - [MST.Symbolic] -> [SVar]; - * - [MST.Unary] -> [Negative], [Sine], [Cosine], [Tangent], [Power], [Log]; - * - [MST.Binary] -> [Sum], [Prod], [Power]. - * - * @receiver the node. - * @param proto the prototype instance. - * @return a scalar function. - */ -public fun > MST.toSFun(): SFun = when (this) { - is MST.Numeric -> toSConst() - is MST.Symbolic -> toSVar() - - is MST.Unary -> when (operation) { - SpaceOperations.PLUS_OPERATION -> +value.toSFun() - SpaceOperations.MINUS_OPERATION -> -value.toSFun() - TrigonometricOperations.SIN_OPERATION -> sin(value.toSFun()) - TrigonometricOperations.COS_OPERATION -> cos(value.toSFun()) - TrigonometricOperations.TAN_OPERATION -> tan(value.toSFun()) - PowerOperations.SQRT_OPERATION -> sqrt(value.toSFun()) - ExponentialOperations.EXP_OPERATION -> exp(value.toSFun()) - ExponentialOperations.LN_OPERATION -> value.toSFun().ln() - else -> error("Unary operation $operation not defined in $this") - } - - is MST.Binary -> when (operation) { - SpaceOperations.PLUS_OPERATION -> left.toSFun() + right.toSFun() - SpaceOperations.MINUS_OPERATION -> left.toSFun() - right.toSFun() - RingOperations.TIMES_OPERATION -> left.toSFun() * right.toSFun() - FieldOperations.DIV_OPERATION -> left.toSFun() / right.toSFun() - PowerOperations.POW_OPERATION -> left.toSFun() pow (right as MST.Numeric).toSConst() - else -> error("Binary operation $operation not defined in $this") - } -} diff --git a/kmath-kotlingrad/src/test/kotlin/kscience/kmath/kotlingrad/AdaptingTests.kt b/kmath-kotlingrad/src/test/kotlin/kscience/kmath/kotlingrad/AdaptingTests.kt deleted file mode 100644 index aa4ddd703..000000000 --- a/kmath-kotlingrad/src/test/kotlin/kscience/kmath/kotlingrad/AdaptingTests.kt +++ /dev/null @@ -1,64 +0,0 @@ -package kscience.kmath.kotlingrad - -import edu.umontreal.kotlingrad.api.* -import kscience.kmath.asm.compile -import kscience.kmath.ast.MstAlgebra -import kscience.kmath.ast.MstExpression -import kscience.kmath.ast.parseMath -import kscience.kmath.expressions.invoke -import kscience.kmath.operations.RealField -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlin.test.fail - -internal class AdaptingTests { - @Test - fun symbol() { - val c1 = MstAlgebra.symbol("x") - assertTrue(c1.toSVar>().name == "x") - val c2 = "kitten".parseMath().toSFun>() - if (c2 is SVar) assertTrue(c2.name == "kitten") else fail() - } - - @Test - fun number() { - val c1 = MstAlgebra.number(12354324) - assertTrue(c1.toSConst().doubleValue == 12354324.0) - val c2 = "0.234".parseMath().toSFun>() - if (c2 is SConst) assertTrue(c2.doubleValue == 0.234) else fail() - val c3 = "1e-3".parseMath().toSFun>() - if (c3 is SConst) assertEquals(0.001, c3.value) else fail() - } - - @Test - fun simpleFunctionShape() { - val linear = "2*x+16".parseMath().toSFun>() - if (linear !is Sum) fail() - if (linear.left !is Prod) fail() - if (linear.right !is SConst) fail() - } - - @Test - fun simpleFunctionDerivative() { - val x = MstAlgebra.symbol("x").toSVar>() - val quadratic = "x^2-4*x-44".parseMath().toSFun>() - val actualDerivative = MstExpression(RealField, quadratic.d(x).toMst()).compile() - val expectedDerivative = MstExpression(RealField, "2*x-4".parseMath()).compile() - assertEquals(actualDerivative("x" to 123.0), expectedDerivative("x" to 123.0)) - } - - @Test - fun moreComplexDerivative() { - val x = MstAlgebra.symbol("x").toSVar>() - val composition = "-sqrt(sin(x^2)-cos(x)^2-16*x)".parseMath().toSFun>() - val actualDerivative = MstExpression(RealField, composition.d(x).toMst()).compile() - - val expectedDerivative = MstExpression( - RealField, - "-(2*x*cos(x^2)+2*sin(x)*cos(x)-16)/(2*sqrt(sin(x^2)-16*x-cos(x)^2))".parseMath() - ).compile() - - assertEquals(actualDerivative("x" to 0.1), expectedDerivative("x" to 0.1)) - } -} diff --git a/kmath-memory/build.gradle.kts b/kmath-memory/build.gradle.kts index 9f92cca92..1f34a4f17 100644 --- a/kmath-memory/build.gradle.kts +++ b/kmath-memory/build.gradle.kts @@ -1,4 +1,3 @@ plugins { - id("ru.mipt.npm.mpp") - id("ru.mipt.npm.native") + id("scientifik.mpp") } diff --git a/kmath-memory/src/commonMain/kotlin/kscience/kmath/memory/Memory.kt b/kmath-memory/src/commonMain/kotlin/scientifik/memory/Memory.kt similarity index 54% rename from kmath-memory/src/commonMain/kotlin/kscience/kmath/memory/Memory.kt rename to kmath-memory/src/commonMain/kotlin/scientifik/memory/Memory.kt index 344a1f1d3..a749a7074 100644 --- a/kmath-memory/src/commonMain/kotlin/kscience/kmath/memory/Memory.kt +++ b/kmath-memory/src/commonMain/kotlin/scientifik/memory/Memory.kt @@ -1,156 +1,148 @@ -package kscience.kmath.memory - -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract +package scientifik.memory /** * Represents a display of certain memory structure. */ -public interface Memory { +interface Memory { /** * The length of this memory in bytes. */ - public val size: Int + val size: Int /** * Get a projection of this memory (it reflects the changes in the parent memory block). */ - public fun view(offset: Int, length: Int): Memory + fun view(offset: Int, length: Int): Memory /** * Creates an independent copy of this memory. */ - public fun copy(): Memory + fun copy(): Memory /** * Gets or creates a reader of this memory. */ - public fun reader(): MemoryReader + fun reader(): MemoryReader /** * Gets or creates a writer of this memory. */ - public fun writer(): MemoryWriter + fun writer(): MemoryWriter - public companion object + companion object } /** * The interface to read primitive types in this memory. */ -public interface MemoryReader { +interface MemoryReader { /** * The underlying memory. */ - public val memory: Memory + val memory: Memory /** * Reads [Double] at certain [offset]. */ - public fun readDouble(offset: Int): Double + fun readDouble(offset: Int): Double /** * Reads [Float] at certain [offset]. */ - public fun readFloat(offset: Int): Float + fun readFloat(offset: Int): Float /** * Reads [Byte] at certain [offset]. */ - public fun readByte(offset: Int): Byte + fun readByte(offset: Int): Byte /** * Reads [Short] at certain [offset]. */ - public fun readShort(offset: Int): Short + fun readShort(offset: Int): Short /** * Reads [Int] at certain [offset]. */ - public fun readInt(offset: Int): Int + fun readInt(offset: Int): Int /** * Reads [Long] at certain [offset]. */ - public fun readLong(offset: Int): Long + fun readLong(offset: Int): Long /** * Disposes this reader if needed. */ - public fun release() + fun release() } /** * Uses the memory for read then releases the reader. */ -public inline fun Memory.read(block: MemoryReader.() -> R): R { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - val reader = reader() - val result = reader.block() - reader.release() - return result +inline fun Memory.read(block: MemoryReader.() -> Unit) { + reader().apply(block).release() } /** * The interface to write primitive types into this memory. */ -public interface MemoryWriter { +interface MemoryWriter { /** * The underlying memory. */ - public val memory: Memory + val memory: Memory /** * Writes [Double] at certain [offset]. */ - public fun writeDouble(offset: Int, value: Double) + fun writeDouble(offset: Int, value: Double) /** * Writes [Float] at certain [offset]. */ - public fun writeFloat(offset: Int, value: Float) + fun writeFloat(offset: Int, value: Float) /** * Writes [Byte] at certain [offset]. */ - public fun writeByte(offset: Int, value: Byte) + fun writeByte(offset: Int, value: Byte) /** * Writes [Short] at certain [offset]. */ - public fun writeShort(offset: Int, value: Short) + fun writeShort(offset: Int, value: Short) /** * Writes [Int] at certain [offset]. */ - public fun writeInt(offset: Int, value: Int) + fun writeInt(offset: Int, value: Int) /** * Writes [Long] at certain [offset]. */ - public fun writeLong(offset: Int, value: Long) + fun writeLong(offset: Int, value: Long) /** * Disposes this writer if needed. */ - public fun release() + fun release() } /** * Uses the memory for write then releases the writer. */ -public inline fun Memory.write(block: MemoryWriter.() -> Unit) { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } +inline fun Memory.write(block: MemoryWriter.() -> Unit) { writer().apply(block).release() } /** * Allocates the most effective platform-specific memory. */ -public expect fun Memory.Companion.allocate(length: Int): Memory +expect fun Memory.Companion.allocate(length: Int): Memory /** * Wraps a [Memory] around existing [ByteArray]. This operation is unsafe since the array is not copied * and could be mutated independently from the resulting [Memory]. */ -public expect fun Memory.Companion.wrap(array: ByteArray): Memory +expect fun Memory.Companion.wrap(array: ByteArray): Memory diff --git a/kmath-memory/src/commonMain/kotlin/kscience/kmath/memory/MemorySpec.kt b/kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt similarity index 53% rename from kmath-memory/src/commonMain/kotlin/kscience/kmath/memory/MemorySpec.kt rename to kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt index 572dab0fa..59a93f290 100644 --- a/kmath-memory/src/commonMain/kotlin/kscience/kmath/memory/MemorySpec.kt +++ b/kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt @@ -1,49 +1,53 @@ -package kscience.kmath.memory +package scientifik.memory /** * A specification to read or write custom objects with fixed size in bytes. * * @param T the type of object this spec manages. */ -public interface MemorySpec { +interface MemorySpec { /** * Size of [T] in bytes after serialization. */ - public val objectSize: Int + val objectSize: Int /** * Reads the object starting from [offset]. */ - public fun MemoryReader.read(offset: Int): T + fun MemoryReader.read(offset: Int): T // TODO consider thread safety /** * Writes the object [value] starting from [offset]. */ - public fun MemoryWriter.write(offset: Int, value: T) + fun MemoryWriter.write(offset: Int, value: T) } /** * Reads the object with [spec] starting from [offset]. */ -public fun MemoryReader.read(spec: MemorySpec, offset: Int): T = with(spec) { read(offset) } +fun MemoryReader.read(spec: MemorySpec, offset: Int): T = with(spec) { read(offset) } /** * Writes the object [value] with [spec] starting from [offset]. */ -public fun MemoryWriter.write(spec: MemorySpec, offset: Int, value: T): Unit = with(spec) { write(offset, value) } +fun MemoryWriter.write(spec: MemorySpec, offset: Int, value: T): Unit = with(spec) { write(offset, value) } /** * Reads array of [size] objects mapped by [spec] at certain [offset]. */ -public inline fun MemoryReader.readArray(spec: MemorySpec, offset: Int, size: Int): Array = - Array(size) { i -> with(spec) { read(offset + i * objectSize) } } +inline fun MemoryReader.readArray(spec: MemorySpec, offset: Int, size: Int): Array = + Array(size) { i -> + spec.run { + read(offset + i * objectSize) + } + } /** * Writes [array] of objects mapped by [spec] at certain [offset]. */ -public fun MemoryWriter.writeArray(spec: MemorySpec, offset: Int, array: Array): Unit = +fun MemoryWriter.writeArray(spec: MemorySpec, offset: Int, array: Array): Unit = with(spec) { array.indices.forEach { i -> write(offset + i * objectSize, array[i]) } } // TODO It is possible to add elastic MemorySpec with unknown object size diff --git a/kmath-memory/src/jsMain/kotlin/kscience/kmath/memory/DataViewMemory.kt b/kmath-memory/src/jsMain/kotlin/scientifik/memory/DataViewMemory.kt similarity index 95% rename from kmath-memory/src/jsMain/kotlin/kscience/kmath/memory/DataViewMemory.kt rename to kmath-memory/src/jsMain/kotlin/scientifik/memory/DataViewMemory.kt index 2146cd4e1..974750502 100644 --- a/kmath-memory/src/jsMain/kotlin/kscience/kmath/memory/DataViewMemory.kt +++ b/kmath-memory/src/jsMain/kotlin/scientifik/memory/DataViewMemory.kt @@ -1,4 +1,4 @@ -package kscience.kmath.memory +package scientifik.memory import org.khronos.webgl.ArrayBuffer import org.khronos.webgl.DataView @@ -83,7 +83,7 @@ private class DataViewMemory(val view: DataView) : Memory { /** * Allocates memory based on a [DataView]. */ -public actual fun Memory.Companion.allocate(length: Int): Memory { +actual fun Memory.Companion.allocate(length: Int): Memory { val buffer = ArrayBuffer(length) return DataViewMemory(DataView(buffer, 0, length)) } @@ -92,7 +92,7 @@ public actual fun Memory.Companion.allocate(length: Int): Memory { * Wraps a [Memory] around existing [ByteArray]. This operation is unsafe since the array is not copied * and could be mutated independently from the resulting [Memory]. */ -public actual fun Memory.Companion.wrap(array: ByteArray): Memory { +actual fun Memory.Companion.wrap(array: ByteArray): Memory { @Suppress("CAST_NEVER_SUCCEEDS") val int8Array = array as Int8Array return DataViewMemory(DataView(int8Array.buffer, int8Array.byteOffset, int8Array.length)) } diff --git a/kmath-memory/src/jvmMain/kotlin/kscience/kmath/memory/ByteBufferMemory.kt b/kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt similarity index 80% rename from kmath-memory/src/jvmMain/kotlin/kscience/kmath/memory/ByteBufferMemory.kt rename to kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt index 7a75b423e..b5a0dd51b 100644 --- a/kmath-memory/src/jvmMain/kotlin/kscience/kmath/memory/ByteBufferMemory.kt +++ b/kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt @@ -1,16 +1,12 @@ -package kscience.kmath.memory +package scientifik.memory -import java.io.IOException import java.nio.ByteBuffer import java.nio.channels.FileChannel import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract -@PublishedApi -internal class ByteBufferMemory( +private class ByteBufferMemory( val buffer: ByteBuffer, val startOffset: Int = 0, override val size: Int = buffer.limit() @@ -93,14 +89,14 @@ internal class ByteBufferMemory( /** * Allocates memory based on a [ByteBuffer]. */ -public actual fun Memory.Companion.allocate(length: Int): Memory = +actual fun Memory.Companion.allocate(length: Int): Memory = ByteBufferMemory(checkNotNull(ByteBuffer.allocate(length))) /** * Wraps a [Memory] around existing [ByteArray]. This operation is unsafe since the array is not copied * and could be mutated independently from the resulting [Memory]. */ -public actual fun Memory.Companion.wrap(array: ByteArray): Memory = ByteBufferMemory(checkNotNull(ByteBuffer.wrap(array))) +actual fun Memory.Companion.wrap(array: ByteArray): Memory = ByteBufferMemory(checkNotNull(ByteBuffer.wrap(array))) /** * Wraps this [ByteBuffer] to [Memory] object. @@ -110,17 +106,13 @@ public actual fun Memory.Companion.wrap(array: ByteArray): Memory = ByteBufferMe * @param size the size of memory to map. * @return the [Memory] object. */ -public fun ByteBuffer.asMemory(startOffset: Int = 0, size: Int = limit()): Memory = +fun ByteBuffer.asMemory(startOffset: Int = 0, size: Int = limit()): Memory = ByteBufferMemory(this, startOffset, size) /** * Uses direct memory-mapped buffer from file to read something and close it afterwards. */ -@Throws(IOException::class) -public inline fun Path.readAsMemory(position: Long = 0, size: Long = Files.size(this), block: Memory.() -> R): R { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - - return FileChannel - .open(this, StandardOpenOption.READ) - .use { ByteBufferMemory(it.map(FileChannel.MapMode.READ_ONLY, position, size)).block() } -} +fun Path.readAsMemory(position: Long = 0, size: Long = Files.size(this), block: Memory.() -> R): R = + FileChannel.open(this, StandardOpenOption.READ).use { + ByteBufferMemory(it.map(FileChannel.MapMode.READ_ONLY, position, size)).block() + } diff --git a/kmath-memory/src/nativeMain/kotlin/kscience/kmath/memory/NativeMemory.kt b/kmath-memory/src/nativeMain/kotlin/kscience/kmath/memory/NativeMemory.kt deleted file mode 100644 index 0e007a8ab..000000000 --- a/kmath-memory/src/nativeMain/kotlin/kscience/kmath/memory/NativeMemory.kt +++ /dev/null @@ -1,93 +0,0 @@ -package kscience.kmath.memory - -@PublishedApi -internal class NativeMemory( - val array: ByteArray, - val startOffset: Int = 0, - override val size: Int = array.size -) : Memory { - @Suppress("NOTHING_TO_INLINE") - private inline fun position(o: Int): Int = startOffset + o - - 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" } - require(offset + length <= size) { "Can't view memory outside the parent region." } - return NativeMemory(array, position(offset), length) - } - - override fun copy(): Memory { - val copy = array.copyOfRange(startOffset, startOffset + size) - return NativeMemory(copy) - } - - private val reader: MemoryReader = object : MemoryReader { - override val memory: Memory get() = this@NativeMemory - - override fun readDouble(offset: Int) = array.getDoubleAt(position(offset)) - - override fun readFloat(offset: Int) = array.getFloatAt(position(offset)) - - override fun readByte(offset: Int) = array[position(offset)] - - override fun readShort(offset: Int) = array.getShortAt(position(offset)) - - override fun readInt(offset: Int) = array.getIntAt(position(offset)) - - override fun readLong(offset: Int) = array.getLongAt(position(offset)) - - override fun release() { - // does nothing on JVM - } - } - - override fun reader(): MemoryReader = reader - - private val writer: MemoryWriter = object : MemoryWriter { - override val memory: Memory get() = this@NativeMemory - - override fun writeDouble(offset: Int, value: Double) { - array.setDoubleAt(position(offset), value) - } - - override fun writeFloat(offset: Int, value: Float) { - array.setFloatAt(position(offset), value) - } - - override fun writeByte(offset: Int, value: Byte) { - array.set(position(offset), value) - } - - override fun writeShort(offset: Int, value: Short) { - array.setShortAt(position(offset), value) - } - - override fun writeInt(offset: Int, value: Int) { - array.setIntAt(position(offset), value) - } - - override fun writeLong(offset: Int, value: Long) { - array.setLongAt(position(offset), value) - } - - override fun release() { - // does nothing on JVM - } - } - - override fun writer(): MemoryWriter = writer -} - -/** - * Wraps a [Memory] around existing [ByteArray]. This operation is unsafe since the array is not copied - * and could be mutated independently from the resulting [Memory]. - */ -public actual fun Memory.Companion.wrap(array: ByteArray): Memory = NativeMemory(array) - -/** - * Allocates the most effective platform-specific memory. - */ -public actual fun Memory.Companion.allocate(length: Int): Memory { - val array = ByteArray(length) - return NativeMemory(array) -} \ No newline at end of file diff --git a/kmath-nd4j/README.md b/kmath-nd4j/README.md deleted file mode 100644 index 176dfc09d..000000000 --- a/kmath-nd4j/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# ND4J NDStructure implementation (`kmath-nd4j`) - -This subproject implements the following features: - - - [nd4jarraystructure](src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt) : NDStructure wrapper for INDArray - - [nd4jarrayrings](src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt) : Rings over Nd4jArrayStructure of Int and Long - - [nd4jarrayfields](src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt) : Fields over Nd4jArrayStructure of Float and Double - - -> #### Artifact: -> -> This module artifact: `kscience.kmath:kmath-nd4j:0.2.0-dev-4`. -> -> Bintray release version: [ ![Download](https://api.bintray.com/packages/mipt-npm/kscience/kmath-nd4j/images/download.svg) ](https://bintray.com/mipt-npm/kscience/kmath-nd4j/_latestVersion) -> -> Bintray development version: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-nd4j/images/download.svg) ](https://bintray.com/mipt-npm/dev/kmath-nd4j/_latestVersion) -> -> **Gradle:** -> -> ```gradle -> repositories { -> maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } -> maven { url 'https://dl.bintray.com/mipt-npm/kscience' } -> maven { url 'https://dl.bintray.com/mipt-npm/dev' } -> maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - -> } -> -> dependencies { -> implementation 'kscience.kmath:kmath-nd4j:0.2.0-dev-4' -> } -> ``` -> **Gradle Kotlin DSL:** -> -> ```kotlin -> repositories { -> maven("https://dl.bintray.com/kotlin/kotlin-eap") -> maven("https://dl.bintray.com/mipt-npm/kscience") -> maven("https://dl.bintray.com/mipt-npm/dev") -> maven("https://dl.bintray.com/hotkeytlt/maven") -> } -> -> dependencies { -> implementation("kscience.kmath:kmath-nd4j:0.2.0-dev-4") -> } -> ``` - -## Examples - -NDStructure wrapper for INDArray: - -```kotlin -import org.nd4j.linalg.factory.* -import scientifik.kmath.nd4j.* -import scientifik.kmath.structures.* - -val array = Nd4j.ones(2, 2).asRealStructure() -println(array[0, 0]) // 1.0 -array[intArrayOf(0, 0)] = 24.0 -println(array[0, 0]) // 24.0 -``` - -Fast element-wise and in-place arithmetics for INDArray: - -```kotlin -import org.nd4j.linalg.factory.* -import scientifik.kmath.nd4j.* -import scientifik.kmath.operations.* - -val field = RealNd4jArrayField(intArrayOf(2, 2)) -val array = Nd4j.rand(2, 2).asRealStructure() - -val res = field { - (25.0 / array + 20) * 4 -} - -println(res.ndArray) -// [[ 250.6449, 428.5840], -// [ 269.7913, 202.2077]] -``` - -Contributed by [Iaroslav Postovalov](https://github.com/CommanderTvis). diff --git a/kmath-nd4j/build.gradle.kts b/kmath-nd4j/build.gradle.kts deleted file mode 100644 index 391727c45..000000000 --- a/kmath-nd4j/build.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -import ru.mipt.npm.gradle.Maturity - -plugins { - id("ru.mipt.npm.jvm") -} - -dependencies { - api(project(":kmath-core")) - api("org.nd4j:nd4j-api:1.0.0-beta7") - testImplementation("org.deeplearning4j:deeplearning4j-core:1.0.0-beta7") - testImplementation("org.nd4j:nd4j-native-platform:1.0.0-beta7") - testImplementation("org.slf4j:slf4j-simple:1.7.30") -} - -readme { - description = "ND4J NDStructure implementation and according NDAlgebra classes" - maturity = Maturity.EXPERIMENTAL - propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) - - feature( - id = "nd4jarraystructure", - description = "NDStructure wrapper for INDArray", - ref = "src/commonMain/kotlin/kscience/kmath/operations/Algebra.kt" - ) - - feature( - id = "nd4jarrayrings", - description = "Rings over Nd4jArrayStructure of Int and Long", - ref = "src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt" - ) - - feature( - id = "nd4jarrayfields", - description = "Fields over Nd4jArrayStructure of Float and Double", - ref = "src/commonMain/kotlin/kscience/kmath/structures/Buffers.kt" - ) -} diff --git a/kmath-nd4j/docs/README-TEMPLATE.md b/kmath-nd4j/docs/README-TEMPLATE.md deleted file mode 100644 index 76ce8c9a7..000000000 --- a/kmath-nd4j/docs/README-TEMPLATE.md +++ /dev/null @@ -1,43 +0,0 @@ -# ND4J NDStructure implementation (`kmath-nd4j`) - -This subproject implements the following features: - -${features} - -${artifact} - -## Examples - -NDStructure wrapper for INDArray: - -```kotlin -import org.nd4j.linalg.factory.* -import scientifik.kmath.nd4j.* -import scientifik.kmath.structures.* - -val array = Nd4j.ones(2, 2).asRealStructure() -println(array[0, 0]) // 1.0 -array[intArrayOf(0, 0)] = 24.0 -println(array[0, 0]) // 24.0 -``` - -Fast element-wise and in-place arithmetics for INDArray: - -```kotlin -import org.nd4j.linalg.factory.* -import scientifik.kmath.nd4j.* -import scientifik.kmath.operations.* - -val field = RealNd4jArrayField(intArrayOf(2, 2)) -val array = Nd4j.rand(2, 2).asRealStructure() - -val res = field { - (25.0 / array + 20) * 4 -} - -println(res.ndArray) -// [[ 250.6449, 428.5840], -// [ 269.7913, 202.2077]] -``` - -Contributed by [Iaroslav Postovalov](https://github.com/CommanderTvis). diff --git a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt deleted file mode 100644 index a8c874fc3..000000000 --- a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt +++ /dev/null @@ -1,349 +0,0 @@ -package kscience.kmath.nd4j - -import kscience.kmath.operations.* -import kscience.kmath.structures.NDAlgebra -import kscience.kmath.structures.NDField -import kscience.kmath.structures.NDRing -import kscience.kmath.structures.NDSpace -import org.nd4j.linalg.api.ndarray.INDArray -import org.nd4j.linalg.factory.Nd4j - -/** - * Represents [NDAlgebra] over [Nd4jArrayAlgebra]. - * - * @param T the type of ND-structure element. - * @param C the type of the element context. - */ -public interface Nd4jArrayAlgebra : NDAlgebra> { - /** - * Wraps [INDArray] to [N]. - */ - public fun INDArray.wrap(): Nd4jArrayStructure - - public override fun produce(initializer: C.(IntArray) -> T): Nd4jArrayStructure { - val struct = Nd4j.create(*shape)!!.wrap() - struct.indicesIterator().forEach { struct[it] = elementContext.initializer(it) } - return struct - } - - public override fun map(arg: Nd4jArrayStructure, transform: C.(T) -> T): Nd4jArrayStructure { - check(arg) - val newStruct = arg.ndArray.dup().wrap() - newStruct.elements().forEach { (idx, value) -> newStruct[idx] = elementContext.transform(value) } - return newStruct - } - - public override fun mapIndexed( - arg: Nd4jArrayStructure, - transform: C.(index: IntArray, T) -> T - ): Nd4jArrayStructure { - check(arg) - val new = Nd4j.create(*shape).wrap() - new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(idx, arg[idx]) } - return new - } - - public override fun combine( - a: Nd4jArrayStructure, - b: Nd4jArrayStructure, - transform: C.(T, T) -> T - ): Nd4jArrayStructure { - check(a, b) - val new = Nd4j.create(*shape).wrap() - new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(a[idx], b[idx]) } - return new - } -} - -/** - * Represents [NDSpace] over [Nd4jArrayStructure]. - * - * @param T the type of the element contained in ND structure. - * @param S the type of space of structure elements. - */ -public interface Nd4jArraySpace : NDSpace>, - Nd4jArrayAlgebra where S : Space { - public override val zero: Nd4jArrayStructure - get() = Nd4j.zeros(*shape).wrap() - - public override fun add(a: Nd4jArrayStructure, b: Nd4jArrayStructure): Nd4jArrayStructure { - check(a, b) - return a.ndArray.add(b.ndArray).wrap() - } - - public override operator fun Nd4jArrayStructure.minus(b: Nd4jArrayStructure): Nd4jArrayStructure { - check(this, b) - return ndArray.sub(b.ndArray).wrap() - } - - public override operator fun Nd4jArrayStructure.unaryMinus(): Nd4jArrayStructure { - check(this) - return ndArray.neg().wrap() - } - - public override fun multiply(a: Nd4jArrayStructure, k: Number): Nd4jArrayStructure { - check(a) - return a.ndArray.mul(k).wrap() - } - - public override operator fun Nd4jArrayStructure.div(k: Number): Nd4jArrayStructure { - check(this) - return ndArray.div(k).wrap() - } - - public override operator fun Nd4jArrayStructure.times(k: Number): Nd4jArrayStructure { - check(this) - return ndArray.mul(k).wrap() - } -} - -/** - * Represents [NDRing] over [Nd4jArrayStructure]. - * - * @param T the type of the element contained in ND structure. - * @param R the type of ring of structure elements. - */ -public interface Nd4jArrayRing : NDRing>, Nd4jArraySpace where R : Ring { - public override val one: Nd4jArrayStructure - get() = Nd4j.ones(*shape).wrap() - - public override fun multiply(a: Nd4jArrayStructure, b: Nd4jArrayStructure): Nd4jArrayStructure { - check(a, b) - return a.ndArray.mul(b.ndArray).wrap() - } - - public override operator fun Nd4jArrayStructure.minus(b: Number): Nd4jArrayStructure { - check(this) - return ndArray.sub(b).wrap() - } - - public override operator fun Nd4jArrayStructure.plus(b: Number): Nd4jArrayStructure { - check(this) - return ndArray.add(b).wrap() - } - - public override operator fun Number.minus(b: Nd4jArrayStructure): Nd4jArrayStructure { - check(b) - return b.ndArray.rsub(this).wrap() - } - - public companion object { - private val intNd4jArrayRingCache: ThreadLocal> = - ThreadLocal.withInitial { hashMapOf() } - - private val longNd4jArrayRingCache: ThreadLocal> = - ThreadLocal.withInitial { hashMapOf() } - - /** - * Creates an [NDRing] for [Int] values or pull it from cache if it was created previously. - */ - public fun int(vararg shape: Int): Nd4jArrayRing = - intNd4jArrayRingCache.get().getOrPut(shape) { IntNd4jArrayRing(shape) } - - /** - * Creates an [NDRing] for [Long] values or pull it from cache if it was created previously. - */ - public fun long(vararg shape: Int): Nd4jArrayRing = - longNd4jArrayRingCache.get().getOrPut(shape) { LongNd4jArrayRing(shape) } - - /** - * Creates a most suitable implementation of [NDRing] using reified class. - */ - @Suppress("UNCHECKED_CAST") - public inline fun auto(vararg shape: Int): Nd4jArrayRing> = when { - T::class == Int::class -> int(*shape) as Nd4jArrayRing> - T::class == Long::class -> long(*shape) as Nd4jArrayRing> - else -> throw UnsupportedOperationException("This factory method only supports Int and Long types.") - } - } -} - -/** - * Represents [NDField] over [Nd4jArrayStructure]. - * - * @param T the type of the element contained in ND structure. - * @param N the type of ND structure. - * @param F the type field of structure elements. - */ -public interface Nd4jArrayField : NDField>, Nd4jArrayRing where F : Field { - public override fun divide(a: Nd4jArrayStructure, b: Nd4jArrayStructure): Nd4jArrayStructure { - check(a, b) - return a.ndArray.div(b.ndArray).wrap() - } - - public override operator fun Number.div(b: Nd4jArrayStructure): Nd4jArrayStructure { - check(b) - return b.ndArray.rdiv(this).wrap() - } - - - public companion object { - private val floatNd4jArrayFieldCache: ThreadLocal> = - ThreadLocal.withInitial { hashMapOf() } - - private val realNd4jArrayFieldCache: ThreadLocal> = - ThreadLocal.withInitial { hashMapOf() } - - /** - * Creates an [NDField] for [Float] values or pull it from cache if it was created previously. - */ - public fun float(vararg shape: Int): Nd4jArrayRing = - floatNd4jArrayFieldCache.get().getOrPut(shape) { FloatNd4jArrayField(shape) } - - /** - * Creates an [NDField] for [Double] values or pull it from cache if it was created previously. - */ - public fun real(vararg shape: Int): Nd4jArrayRing = - realNd4jArrayFieldCache.get().getOrPut(shape) { RealNd4jArrayField(shape) } - - /** - * Creates a most suitable implementation of [NDRing] using reified class. - */ - @Suppress("UNCHECKED_CAST") - public inline fun auto(vararg shape: Int): Nd4jArrayField> = when { - T::class == Float::class -> float(*shape) as Nd4jArrayField> - T::class == Double::class -> real(*shape) as Nd4jArrayField> - else -> throw UnsupportedOperationException("This factory method only supports Float and Double types.") - } - } -} - -/** - * Represents [NDField] over [Nd4jArrayRealStructure]. - */ -public class RealNd4jArrayField(public override val shape: IntArray) : Nd4jArrayField { - public override val elementContext: RealField - get() = RealField - - public override fun INDArray.wrap(): Nd4jArrayStructure = check(asRealStructure()) - - public override operator fun Nd4jArrayStructure.div(arg: Double): Nd4jArrayStructure { - check(this) - return ndArray.div(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.plus(arg: Double): Nd4jArrayStructure { - check(this) - return ndArray.add(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.minus(arg: Double): Nd4jArrayStructure { - check(this) - return ndArray.sub(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.times(arg: Double): Nd4jArrayStructure { - check(this) - return ndArray.mul(arg).wrap() - } - - public override operator fun Double.div(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) - return arg.ndArray.rdiv(this).wrap() - } - - public override operator fun Double.minus(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) - return arg.ndArray.rsub(this).wrap() - } -} - -/** - * Represents [NDField] over [Nd4jArrayStructure] of [Float]. - */ -public class FloatNd4jArrayField(public override val shape: IntArray) : Nd4jArrayField { - public override val elementContext: FloatField - get() = FloatField - - public override fun INDArray.wrap(): Nd4jArrayStructure = check(asFloatStructure()) - - public override operator fun Nd4jArrayStructure.div(arg: Float): Nd4jArrayStructure { - check(this) - return ndArray.div(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.plus(arg: Float): Nd4jArrayStructure { - check(this) - return ndArray.add(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.minus(arg: Float): Nd4jArrayStructure { - check(this) - return ndArray.sub(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.times(arg: Float): Nd4jArrayStructure { - check(this) - return ndArray.mul(arg).wrap() - } - - public override operator fun Float.div(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) - return arg.ndArray.rdiv(this).wrap() - } - - public override operator fun Float.minus(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) - return arg.ndArray.rsub(this).wrap() - } -} - -/** - * Represents [NDRing] over [Nd4jArrayIntStructure]. - */ -public class IntNd4jArrayRing(public override val shape: IntArray) : Nd4jArrayRing { - public override val elementContext: IntRing - get() = IntRing - - public override fun INDArray.wrap(): Nd4jArrayStructure = check(asIntStructure()) - - public override operator fun Nd4jArrayStructure.plus(arg: Int): Nd4jArrayStructure { - check(this) - return ndArray.add(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.minus(arg: Int): Nd4jArrayStructure { - check(this) - return ndArray.sub(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.times(arg: Int): Nd4jArrayStructure { - check(this) - return ndArray.mul(arg).wrap() - } - - public override operator fun Int.minus(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) - return arg.ndArray.rsub(this).wrap() - } -} - -/** - * Represents [NDRing] over [Nd4jArrayStructure] of [Long]. - */ -public class LongNd4jArrayRing(public override val shape: IntArray) : Nd4jArrayRing { - public override val elementContext: LongRing - get() = LongRing - - public override fun INDArray.wrap(): Nd4jArrayStructure = check(asLongStructure()) - - public override operator fun Nd4jArrayStructure.plus(arg: Long): Nd4jArrayStructure { - check(this) - return ndArray.add(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.minus(arg: Long): Nd4jArrayStructure { - check(this) - return ndArray.sub(arg).wrap() - } - - public override operator fun Nd4jArrayStructure.times(arg: Long): Nd4jArrayStructure { - check(this) - return ndArray.mul(arg).wrap() - } - - public override operator fun Long.minus(arg: Nd4jArrayStructure): Nd4jArrayStructure { - check(arg) - return arg.ndArray.rsub(this).wrap() - } -} diff --git a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayIterator.kt b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayIterator.kt deleted file mode 100644 index 1463a92fe..000000000 --- a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayIterator.kt +++ /dev/null @@ -1,62 +0,0 @@ -package kscience.kmath.nd4j - -import org.nd4j.linalg.api.ndarray.INDArray -import org.nd4j.linalg.api.shape.Shape - -private class Nd4jArrayIndicesIterator(private val iterateOver: INDArray) : Iterator { - private var i: Int = 0 - - override fun hasNext(): Boolean = i < iterateOver.length() - - override fun next(): IntArray { - val la = if (iterateOver.ordering() == 'c') - Shape.ind2subC(iterateOver, i++.toLong())!! - else - Shape.ind2sub(iterateOver, i++.toLong())!! - - return la.toIntArray() - } -} - -internal fun INDArray.indicesIterator(): Iterator = Nd4jArrayIndicesIterator(this) - -private sealed class Nd4jArrayIteratorBase(protected val iterateOver: INDArray) : Iterator> { - private var i: Int = 0 - - final override fun hasNext(): Boolean = i < iterateOver.length() - - abstract fun getSingle(indices: LongArray): T - - final override fun next(): Pair { - val la = if (iterateOver.ordering() == 'c') - Shape.ind2subC(iterateOver, i++.toLong())!! - else - Shape.ind2sub(iterateOver, i++.toLong())!! - - return la.toIntArray() to getSingle(la) - } -} - -private class Nd4jArrayRealIterator(iterateOver: INDArray) : Nd4jArrayIteratorBase(iterateOver) { - override fun getSingle(indices: LongArray): Double = iterateOver.getDouble(*indices) -} - -internal fun INDArray.realIterator(): Iterator> = Nd4jArrayRealIterator(this) - -private class Nd4jArrayLongIterator(iterateOver: INDArray) : Nd4jArrayIteratorBase(iterateOver) { - override fun getSingle(indices: LongArray) = iterateOver.getLong(*indices) -} - -internal fun INDArray.longIterator(): Iterator> = Nd4jArrayLongIterator(this) - -private class Nd4jArrayIntIterator(iterateOver: INDArray) : Nd4jArrayIteratorBase(iterateOver) { - override fun getSingle(indices: LongArray) = iterateOver.getInt(*indices.toIntArray()) -} - -internal fun INDArray.intIterator(): Iterator> = Nd4jArrayIntIterator(this) - -private class Nd4jArrayFloatIterator(iterateOver: INDArray) : Nd4jArrayIteratorBase(iterateOver) { - override fun getSingle(indices: LongArray) = iterateOver.getFloat(*indices) -} - -internal fun INDArray.floatIterator(): Iterator> = Nd4jArrayFloatIterator(this) diff --git a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayStructure.kt b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayStructure.kt deleted file mode 100644 index d47a293c3..000000000 --- a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayStructure.kt +++ /dev/null @@ -1,68 +0,0 @@ -package kscience.kmath.nd4j - -import kscience.kmath.structures.MutableNDStructure -import kscience.kmath.structures.NDStructure -import org.nd4j.linalg.api.ndarray.INDArray - -/** - * Represents a [NDStructure] wrapping an [INDArray] object. - * - * @param T the type of items. - */ -public sealed class Nd4jArrayStructure : MutableNDStructure { - /** - * The wrapped [INDArray]. - */ - public abstract val ndArray: INDArray - - public override val shape: IntArray - get() = ndArray.shape().toIntArray() - - internal abstract fun elementsIterator(): Iterator> - internal fun indicesIterator(): Iterator = ndArray.indicesIterator() - public override fun elements(): Sequence> = Sequence(::elementsIterator) -} - -private data class Nd4jArrayIntStructure(override val ndArray: INDArray) : Nd4jArrayStructure() { - override fun elementsIterator(): Iterator> = ndArray.intIterator() - override fun get(index: IntArray): Int = ndArray.getInt(*index) - override fun set(index: IntArray, value: Int): Unit = run { ndArray.putScalar(index, value) } -} - -/** - * Wraps this [INDArray] to [Nd4jArrayStructure]. - */ -public fun INDArray.asIntStructure(): Nd4jArrayStructure = Nd4jArrayIntStructure(this) - -private data class Nd4jArrayLongStructure(override val ndArray: INDArray) : Nd4jArrayStructure() { - override fun elementsIterator(): Iterator> = ndArray.longIterator() - override fun get(index: IntArray): Long = ndArray.getLong(*index.toLongArray()) - override fun set(index: IntArray, value: Long): Unit = run { ndArray.putScalar(index, value.toDouble()) } -} - -/** - * Wraps this [INDArray] to [Nd4jArrayStructure]. - */ -public fun INDArray.asLongStructure(): Nd4jArrayStructure = Nd4jArrayLongStructure(this) - -private data class Nd4jArrayRealStructure(override val ndArray: INDArray) : Nd4jArrayStructure() { - override fun elementsIterator(): Iterator> = ndArray.realIterator() - override fun get(index: IntArray): Double = ndArray.getDouble(*index) - override fun set(index: IntArray, value: Double): Unit = run { ndArray.putScalar(index, value) } -} - -/** - * Wraps this [INDArray] to [Nd4jArrayStructure]. - */ -public fun INDArray.asRealStructure(): Nd4jArrayStructure = Nd4jArrayRealStructure(this) - -private data class Nd4jArrayFloatStructure(override val ndArray: INDArray) : Nd4jArrayStructure() { - override fun elementsIterator(): Iterator> = ndArray.floatIterator() - override fun get(index: IntArray): Float = ndArray.getFloat(*index) - override fun set(index: IntArray, value: Float): Unit = run { ndArray.putScalar(index, value) } -} - -/** - * Wraps this [INDArray] to [Nd4jArrayStructure]. - */ -public fun INDArray.asFloatStructure(): Nd4jArrayStructure = Nd4jArrayFloatStructure(this) diff --git a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/arrays.kt b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/arrays.kt deleted file mode 100644 index 798f81c35..000000000 --- a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/arrays.kt +++ /dev/null @@ -1,4 +0,0 @@ -package kscience.kmath.nd4j - -internal fun IntArray.toLongArray(): LongArray = LongArray(size) { this[it].toLong() } -internal fun LongArray.toIntArray(): IntArray = IntArray(size) { this[it].toInt() } diff --git a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt b/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt deleted file mode 100644 index 650d5670c..000000000 --- a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package kscience.kmath.nd4j - -import org.nd4j.linalg.factory.Nd4j -import kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.fail - -internal class Nd4jArrayAlgebraTest { - @Test - fun testProduce() { - val res = (RealNd4jArrayField(intArrayOf(2, 2))) { produce { it.sum().toDouble() } } - val expected = (Nd4j.create(2, 2) ?: fail()).asRealStructure() - expected[intArrayOf(0, 0)] = 0.0 - expected[intArrayOf(0, 1)] = 1.0 - expected[intArrayOf(1, 0)] = 1.0 - expected[intArrayOf(1, 1)] = 2.0 - assertEquals(expected, res) - } - - @Test - fun testMap() { - val res = (IntNd4jArrayRing(intArrayOf(2, 2))) { map(one) { it + it * 2 } } - val expected = (Nd4j.create(2, 2) ?: fail()).asIntStructure() - expected[intArrayOf(0, 0)] = 3 - expected[intArrayOf(0, 1)] = 3 - expected[intArrayOf(1, 0)] = 3 - expected[intArrayOf(1, 1)] = 3 - assertEquals(expected, res) - } - - @Test - fun testAdd() { - val res = (IntNd4jArrayRing(intArrayOf(2, 2))) { one + 25 } - val expected = (Nd4j.create(2, 2) ?: fail()).asIntStructure() - expected[intArrayOf(0, 0)] = 26 - expected[intArrayOf(0, 1)] = 26 - expected[intArrayOf(1, 0)] = 26 - expected[intArrayOf(1, 1)] = 26 - assertEquals(expected, res) - } -} diff --git a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt b/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt deleted file mode 100644 index 7e46211c1..000000000 --- a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt +++ /dev/null @@ -1,72 +0,0 @@ -package kscience.kmath.nd4j - -import kscience.kmath.structures.get -import org.nd4j.linalg.factory.Nd4j -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import kotlin.test.fail - -internal class Nd4jArrayStructureTest { - @Test - fun testElements() { - val nd = Nd4j.create(doubleArrayOf(1.0, 2.0, 3.0))!! - val struct = nd.asRealStructure() - val res = struct.elements().map(Pair::second).toList() - assertEquals(listOf(1.0, 2.0, 3.0), res) - } - - @Test - fun testShape() { - val nd = Nd4j.rand(10, 2, 3, 6) ?: fail() - val struct = nd.asRealStructure() - assertEquals(intArrayOf(10, 2, 3, 6).toList(), struct.shape.toList()) - } - - @Test - fun testEquals() { - val nd1 = Nd4j.create(doubleArrayOf(1.0, 2.0, 3.0)) ?: fail() - val struct1 = nd1.asRealStructure() - assertEquals(struct1, struct1) - assertNotEquals(struct1 as Any?, null) - val nd2 = Nd4j.create(doubleArrayOf(1.0, 2.0, 3.0)) ?: fail() - val struct2 = nd2.asRealStructure() - assertEquals(struct1, struct2) - assertEquals(struct2, struct1) - val nd3 = Nd4j.create(doubleArrayOf(1.0, 2.0, 3.0)) ?: fail() - val struct3 = nd3.asRealStructure() - assertEquals(struct2, struct3) - assertEquals(struct1, struct3) - } - - @Test - fun testHashCode() { - val nd1 = Nd4j.create(doubleArrayOf(1.0, 2.0, 3.0))?:fail() - val struct1 = nd1.asRealStructure() - val nd2 = Nd4j.create(doubleArrayOf(1.0, 2.0, 3.0))?:fail() - val struct2 = nd2.asRealStructure() - assertEquals(struct1.hashCode(), struct2.hashCode()) - } - - @Test - fun testDimension() { - val nd = Nd4j.rand(8, 16, 3, 7, 1)!! - val struct = nd.asFloatStructure() - assertEquals(5, struct.dimension) - } - - @Test - fun testGet() { - val nd = Nd4j.rand(10, 2, 3, 6)?:fail() - val struct = nd.asIntStructure() - assertEquals(nd.getInt(0, 0, 0, 0), struct[0, 0, 0, 0]) - } - - @Test - fun testSet() { - val nd = Nd4j.rand(17, 12, 4, 8)!! - val struct = nd.asLongStructure() - struct[intArrayOf(1, 2, 3, 4)] = 777 - assertEquals(777, struct[1, 2, 3, 4]) - } -} diff --git a/kmath-stat/build.gradle.kts b/kmath-prob/build.gradle.kts similarity index 80% rename from kmath-stat/build.gradle.kts rename to kmath-prob/build.gradle.kts index 186aff944..a69d61b73 100644 --- a/kmath-stat/build.gradle.kts +++ b/kmath-prob/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("ru.mipt.npm.mpp") + id("scientifik.mpp") } kotlin.sourceSets { @@ -8,11 +8,10 @@ kotlin.sourceSets { api(project(":kmath-coroutines")) } } - - jvmMain { - dependencies { + jvmMain{ + dependencies{ api("org.apache.commons:commons-rng-sampling:1.3") api("org.apache.commons:commons-rng-simple:1.3") } } -} +} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/Distribution.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt similarity index 60% rename from kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/Distribution.kt rename to kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt index c4ceb29eb..3b874adaa 100644 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/Distribution.kt +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt @@ -1,23 +1,23 @@ -package kscience.kmath.stat +package scientifik.kmath.prob -import kscience.kmath.chains.Chain -import kscience.kmath.chains.collect -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.BufferFactory +import scientifik.kmath.chains.Chain +import scientifik.kmath.chains.collect +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory -public interface Sampler { - public fun sample(generator: RandomGenerator): Chain +interface Sampler { + fun sample(generator: RandomGenerator): Chain } /** * A distribution of typed objects */ -public interface Distribution : Sampler { +interface Distribution : Sampler { /** * A probability value for given argument [arg]. * For continuous distributions returns PDF */ - public fun probability(arg: T): Double + fun probability(arg: T): Double /** * Create a chain of samples from this distribution. @@ -28,20 +28,20 @@ public interface Distribution : Sampler { /** * An empty companion. Distribution factories should be written as its extensions */ - public companion object + companion object } -public interface UnivariateDistribution> : Distribution { +interface UnivariateDistribution> : Distribution { /** * Cumulative distribution for ordered parameter (CDF) */ - public fun cumulative(arg: T): Double + fun cumulative(arg: T): Double } /** * Compute probability integral in an interval */ -public fun > UnivariateDistribution.integral(from: T, to: T): Double { +fun > UnivariateDistribution.integral(from: T, to: T): Double { require(to > from) return cumulative(to) - cumulative(from) } @@ -49,7 +49,7 @@ public fun > UnivariateDistribution.integral(from: T, to: T /** * Sample a bunch of values */ -public fun Sampler.sampleBuffer( +fun Sampler.sampleBuffer( generator: RandomGenerator, size: Int, bufferFactory: BufferFactory = Buffer.Companion::boxing @@ -57,12 +57,11 @@ public fun Sampler.sampleBuffer( require(size > 1) //creating temporary storage once val tmp = ArrayList(size) - return sample(generator).collect { chain -> //clear list from previous run tmp.clear() //Fill list - repeat(size) { + repeat(size){ tmp.add(chain.next()) } //return new buffer with elements from tmp @@ -73,5 +72,5 @@ public fun Sampler.sampleBuffer( /** * Generate a bunch of samples from real distributions */ -public fun Sampler.sampleBuffer(generator: RandomGenerator, size: Int): Chain> = - sampleBuffer(generator, size, Buffer.Companion::real) +fun Sampler.sampleBuffer(generator: RandomGenerator, size: Int) = + sampleBuffer(generator, size, Buffer.Companion::real) \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/FactorizedDistribution.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/FactorizedDistribution.kt new file mode 100644 index 000000000..ea526c058 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/FactorizedDistribution.kt @@ -0,0 +1,47 @@ +package scientifik.kmath.prob + +import scientifik.kmath.chains.Chain +import scientifik.kmath.chains.SimpleChain + +/** + * A multivariate distribution which takes a map of parameters + */ +interface NamedDistribution : Distribution> + +/** + * A multivariate distribution that has independent distributions for separate axis + */ +class FactorizedDistribution(val distributions: Collection>) : NamedDistribution { + + override fun probability(arg: Map): Double { + return distributions.fold(1.0) { acc, distr -> acc * distr.probability(arg) } + } + + override fun sample(generator: RandomGenerator): Chain> { + val chains = distributions.map { it.sample(generator) } + return SimpleChain> { + chains.fold(emptyMap()) { acc, chain -> acc + chain.next() } + } + } +} + +class NamedDistributionWrapper(val name: String, val distribution: Distribution) : NamedDistribution { + override fun probability(arg: Map): Double = distribution.probability( + arg[name] ?: error("Argument with name $name not found in input parameters") + ) + + override fun sample(generator: RandomGenerator): Chain> { + val chain = distribution.sample(generator) + return SimpleChain { + mapOf(name to chain.next()) + } + } +} + +class DistributionBuilder{ + private val distributions = ArrayList>() + + infix fun String.to(distribution: Distribution){ + distributions.add(NamedDistributionWrapper(this,distribution)) + } +} \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt new file mode 100644 index 000000000..47fc6e4c5 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt @@ -0,0 +1,14 @@ +package scientifik.kmath.prob + +import scientifik.kmath.chains.Chain + +/** + * A possibly stateful chain producing random values. + */ +class RandomChain(val generator: RandomGenerator, private val gen: suspend RandomGenerator.() -> R) : Chain { + override suspend fun next(): R = generator.gen() + + override fun fork(): Chain = RandomChain(generator.fork(), gen) +} + +fun RandomGenerator.chain(gen: suspend RandomGenerator.() -> R): RandomChain = RandomChain(this, gen) \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomGenerator.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomGenerator.kt new file mode 100644 index 000000000..2a225fe47 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomGenerator.kt @@ -0,0 +1,55 @@ +package scientifik.kmath.prob + +import kotlin.random.Random + +/** + * A basic generator + */ +interface RandomGenerator { + fun nextBoolean(): Boolean + + fun nextDouble(): Double + fun nextInt(): Int + fun nextInt(until: Int): Int + fun nextLong(): Long + fun nextLong(until: Long): Long + + fun fillBytes(array: ByteArray, fromIndex: Int = 0, toIndex: Int = array.size) + fun nextBytes(size: Int): ByteArray = ByteArray(size).also { fillBytes(it) } + + /** + * Create a new generator which is independent from current generator (operations on new generator do not affect this one + * and vise versa). The statistical properties of new generator should be the same as for this one. + * For pseudo-random generator, the fork is keeping the same sequence of numbers for given call order for each run. + * + * The thread safety of this operation is not guaranteed since it could affect the state of the generator. + */ + fun fork(): RandomGenerator + + companion object { + val default by lazy { DefaultGenerator() } + + fun default(seed: Long) = DefaultGenerator(Random(seed)) + } +} + +inline class DefaultGenerator(val random: Random = Random) : RandomGenerator { + override fun nextBoolean(): Boolean = random.nextBoolean() + + override fun nextDouble(): Double = random.nextDouble() + + override fun nextInt(): Int = random.nextInt() + override fun nextInt(until: Int): Int = random.nextInt(until) + + override fun nextLong(): Long = random.nextLong() + + override fun nextLong(until: Long): Long = random.nextLong(until) + + override fun fillBytes(array: ByteArray, fromIndex: Int, toIndex: Int) { + random.nextBytes(array, fromIndex, toIndex) + } + + override fun nextBytes(size: Int): ByteArray = random.nextBytes(size) + + override fun fork(): RandomGenerator = RandomGenerator.default(random.nextLong()) +} \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/SamplerAlgebra.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/SamplerAlgebra.kt new file mode 100644 index 000000000..3a60c0bda --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/SamplerAlgebra.kt @@ -0,0 +1,31 @@ +package scientifik.kmath.prob + +import scientifik.kmath.chains.Chain +import scientifik.kmath.chains.ConstantChain +import scientifik.kmath.chains.map +import scientifik.kmath.chains.zip +import scientifik.kmath.operations.Space + +class BasicSampler(val chainBuilder: (RandomGenerator) -> Chain) : Sampler { + override fun sample(generator: RandomGenerator): Chain = chainBuilder(generator) +} + +class ConstantSampler(val value: T) : Sampler { + override fun sample(generator: RandomGenerator): Chain = ConstantChain(value) +} + +/** + * A space for samplers. Allows to perform simple operations on distributions + */ +class SamplerSpace(val space: Space) : Space> { + + override val zero: Sampler = ConstantSampler(space.zero) + + override fun add(a: Sampler, b: Sampler): Sampler = BasicSampler { generator -> + a.sample(generator).zip(b.sample(generator)) { aValue, bValue -> space.run { aValue + bValue } } + } + + override fun multiply(a: Sampler, k: Number): Sampler = BasicSampler { generator -> + a.sample(generator).map { space.run { it * k.toDouble() } } + } +} \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt new file mode 100644 index 000000000..804aed089 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt @@ -0,0 +1,95 @@ +package scientifik.kmath.prob + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.scanReduce +import scientifik.kmath.coroutines.mapParallel +import scientifik.kmath.operations.* +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.asIterable +import scientifik.kmath.structures.asSequence + +/** + * A function, that transforms a buffer of random quantities to some resulting value + */ +interface Statistic { + suspend operator fun invoke(data: Buffer): R +} + +/** + * A statistic tha could be computed separately on different blocks of data and then composed + * @param T - source type + * @param I - intermediate block type + * @param R - result type + */ +interface ComposableStatistic : Statistic { + //compute statistic on a single block + suspend fun computeIntermediate(data: Buffer): I + //Compose two blocks + suspend fun composeIntermediate(first: I, second: I): I + //Transform block to result + suspend fun toResult(intermediate: I): R + + override suspend fun invoke(data: Buffer): R = toResult(computeIntermediate(data)) +} + +@FlowPreview +@ExperimentalCoroutinesApi +private fun ComposableStatistic.flowIntermediate( + flow: Flow>, + dispatcher: CoroutineDispatcher = Dispatchers.Default +): Flow = flow + .mapParallel(dispatcher) { computeIntermediate(it) } + .scanReduce(::composeIntermediate) + + +/** + * Perform a streaming statistical analysis on a chunked data. The computation of inner representation is done in parallel + * if [dispatcher] allows it. + * + * The resulting flow contains values that include the whole previous statistics, not only the last chunk. + */ +@FlowPreview +@ExperimentalCoroutinesApi +fun ComposableStatistic.flow( + flow: Flow>, + dispatcher: CoroutineDispatcher = Dispatchers.Default +): Flow = flowIntermediate(flow,dispatcher).map(::toResult) + +/** + * Arithmetic mean + */ +class Mean(val space: Space) : ComposableStatistic, T> { + override suspend fun computeIntermediate(data: Buffer): Pair = + space.run { sum(data.asIterable()) } to data.size + + override suspend fun composeIntermediate(first: Pair, second: Pair): Pair = + space.run { first.first + second.first } to (first.second + second.second) + + override suspend fun toResult(intermediate: Pair): T = + space.run { intermediate.first / intermediate.second } + + companion object { + //TODO replace with optimized version which respects overflow + val real = Mean(RealField) + val int = Mean(IntRing) + val long = Mean(LongRing) + } +} + +/** + * Non-composable median + */ +class Median(private val comparator: Comparator) : Statistic { + override suspend fun invoke(data: Buffer): T { + return data.asSequence().sortedWith(comparator).toList()[data.size / 2] //TODO check if this is correct + } + + companion object { + val real = Median(Comparator { a: Double, b: Double -> a.compareTo(b) }) + } +} \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/UniformDistribution.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/UniformDistribution.kt new file mode 100644 index 000000000..9d96bff59 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/UniformDistribution.kt @@ -0,0 +1,34 @@ +package scientifik.kmath.prob + +import scientifik.kmath.chains.Chain +import scientifik.kmath.chains.SimpleChain + +class UniformDistribution(val range: ClosedFloatingPointRange) : UnivariateDistribution { + + private val length = range.endInclusive - range.start + + override fun probability(arg: Double): Double { + return if (arg in range) { + return 1.0 / length + } else { + 0.0 + } + } + + override fun sample(generator: RandomGenerator): Chain { + return SimpleChain { + range.start + generator.nextDouble() * length + } + } + + override fun cumulative(arg: Double): Double { + return when { + arg < range.start -> 0.0 + arg >= range.endInclusive -> 1.0 + else -> (arg - range.start) / length + } + } +} + +fun Distribution.Companion.uniform(range: ClosedFloatingPointRange): UniformDistribution = + UniformDistribution(range) \ No newline at end of file diff --git a/kmath-prob/src/jvmMain/kotlin/scientifik/kmath/prob/RandomSourceGenerator.kt b/kmath-prob/src/jvmMain/kotlin/scientifik/kmath/prob/RandomSourceGenerator.kt new file mode 100644 index 000000000..f5a73a08b --- /dev/null +++ b/kmath-prob/src/jvmMain/kotlin/scientifik/kmath/prob/RandomSourceGenerator.kt @@ -0,0 +1,67 @@ +package scientifik.kmath.prob + +import org.apache.commons.rng.UniformRandomProvider +import org.apache.commons.rng.simple.RandomSource + +class RandomSourceGenerator(val source: RandomSource, seed: Long?) : RandomGenerator { + internal val random: UniformRandomProvider = seed?.let { + RandomSource.create(source, seed) + } ?: RandomSource.create(source) + + override fun nextBoolean(): Boolean = random.nextBoolean() + + override fun nextDouble(): Double = random.nextDouble() + + override fun nextInt(): Int = random.nextInt() + override fun nextInt(until: Int): Int = random.nextInt(until) + + override fun nextLong(): Long = random.nextLong() + override fun nextLong(until: Long): Long = random.nextLong(until) + + override fun fillBytes(array: ByteArray, fromIndex: Int, toIndex: Int) { + require(toIndex > fromIndex) + random.nextBytes(array, fromIndex, toIndex - fromIndex) + } + + override fun fork(): RandomGenerator = RandomSourceGenerator(source, nextLong()) +} + +inline class RandomGeneratorProvider(val generator: RandomGenerator) : UniformRandomProvider { + override fun nextBoolean(): Boolean = generator.nextBoolean() + + override fun nextFloat(): Float = generator.nextDouble().toFloat() + + override fun nextBytes(bytes: ByteArray) { + generator.fillBytes(bytes) + } + + override fun nextBytes(bytes: ByteArray, start: Int, len: Int) { + generator.fillBytes(bytes, start, start + len) + } + + override fun nextInt(): Int = generator.nextInt() + + override fun nextInt(n: Int): Int = generator.nextInt(n) + + override fun nextDouble(): Double = generator.nextDouble() + + override fun nextLong(): Long = generator.nextLong() + + override fun nextLong(n: Long): Long = generator.nextLong(n) +} + +/** + * Represent this [RandomGenerator] as commons-rng [UniformRandomProvider] preserving and mirroring its current state. + * Getting new value from one of those changes the state of another. + */ +fun RandomGenerator.asUniformRandomProvider(): UniformRandomProvider = if (this is RandomSourceGenerator) { + random +} else { + RandomGeneratorProvider(this) +} + +fun RandomGenerator.Companion.fromSource(source: RandomSource, seed: Long? = null): RandomSourceGenerator = + RandomSourceGenerator(source, seed) + +fun RandomGenerator.Companion.mersenneTwister(seed: Long? = null): RandomSourceGenerator = + fromSource(RandomSource.MT, seed) diff --git a/kmath-stat/src/jvmMain/kotlin/kscience/kmath/stat/distributions.kt b/kmath-prob/src/jvmMain/kotlin/scientifik/kmath/prob/distributions.kt similarity index 53% rename from kmath-stat/src/jvmMain/kotlin/kscience/kmath/stat/distributions.kt rename to kmath-prob/src/jvmMain/kotlin/scientifik/kmath/prob/distributions.kt index 6cc18a37c..412454994 100644 --- a/kmath-stat/src/jvmMain/kotlin/kscience/kmath/stat/distributions.kt +++ b/kmath-prob/src/jvmMain/kotlin/scientifik/kmath/prob/distributions.kt @@ -1,42 +1,47 @@ -package kscience.kmath.stat +package scientifik.kmath.prob -import kscience.kmath.chains.BlockingIntChain -import kscience.kmath.chains.BlockingRealChain -import kscience.kmath.chains.Chain import org.apache.commons.rng.UniformRandomProvider import org.apache.commons.rng.sampling.distribution.* +import scientifik.kmath.chains.BlockingIntChain +import scientifik.kmath.chains.BlockingRealChain +import scientifik.kmath.chains.Chain +import java.util.* import kotlin.math.PI import kotlin.math.exp import kotlin.math.pow import kotlin.math.sqrt -public abstract class ContinuousSamplerDistribution : Distribution { +abstract class ContinuousSamplerDistribution : Distribution { + private inner class ContinuousSamplerChain(val generator: RandomGenerator) : BlockingRealChain() { private val sampler = buildCMSampler(generator) override fun nextDouble(): Double = sampler.sample() + override fun fork(): Chain = ContinuousSamplerChain(generator.fork()) } protected abstract fun buildCMSampler(generator: RandomGenerator): ContinuousSampler - public override fun sample(generator: RandomGenerator): BlockingRealChain = ContinuousSamplerChain(generator) + override fun sample(generator: RandomGenerator): BlockingRealChain = ContinuousSamplerChain(generator) } -public abstract class DiscreteSamplerDistribution : Distribution { +abstract class DiscreteSamplerDistribution : Distribution { + private inner class ContinuousSamplerChain(val generator: RandomGenerator) : BlockingIntChain() { private val sampler = buildSampler(generator) override fun nextInt(): Int = sampler.sample() + override fun fork(): Chain = ContinuousSamplerChain(generator.fork()) } protected abstract fun buildSampler(generator: RandomGenerator): DiscreteSampler - public override fun sample(generator: RandomGenerator): BlockingIntChain = ContinuousSamplerChain(generator) + override fun sample(generator: RandomGenerator): BlockingIntChain = ContinuousSamplerChain(generator) } -public enum class NormalSamplerMethod { +enum class NormalSamplerMethod { BoxMuller, Marsaglia, Ziggurat @@ -49,21 +54,20 @@ private fun normalSampler(method: NormalSamplerMethod, provider: UniformRandomPr NormalSamplerMethod.Ziggurat -> ZigguratNormalizedGaussianSampler(provider) } -public fun Distribution.Companion.normal( +fun Distribution.Companion.normal( method: NormalSamplerMethod = NormalSamplerMethod.Ziggurat -): ContinuousSamplerDistribution = object : ContinuousSamplerDistribution() { +): Distribution = object : ContinuousSamplerDistribution() { override fun buildCMSampler(generator: RandomGenerator): ContinuousSampler { - val provider = generator.asUniformRandomProvider() + val provider: UniformRandomProvider = generator.asUniformRandomProvider() return normalSampler(method, provider) } - override fun probability(arg: Double): Double = exp(-arg.pow(2) / 2) / sqrt(PI * 2) + override fun probability(arg: Double): Double { + return exp(-arg.pow(2) / 2) / sqrt(PI * 2) + } } -/** - * A univariate normal distribution with given [mean] and [sigma]. [method] defines commons-rng generation method - */ -public fun Distribution.Companion.normal( +fun Distribution.Companion.normal( mean: Double, sigma: Double, method: NormalSamplerMethod = NormalSamplerMethod.Ziggurat @@ -72,27 +76,34 @@ public fun Distribution.Companion.normal( private val norm = sigma * sqrt(PI * 2) override fun buildCMSampler(generator: RandomGenerator): ContinuousSampler { - val provider = generator.asUniformRandomProvider() + val provider: UniformRandomProvider = generator.asUniformRandomProvider() val normalizedSampler = normalSampler(method, provider) return GaussianSampler(normalizedSampler, mean, sigma) } - override fun probability(arg: Double): Double = exp(-(arg - mean).pow(2) / 2 / sigma2) / norm + override fun probability(arg: Double): Double { + return exp(-(arg - mean).pow(2) / 2 / sigma2) / norm + } } -public fun Distribution.Companion.poisson(lambda: Double): DiscreteSamplerDistribution = - object : DiscreteSamplerDistribution() { - private val computedProb: MutableMap = hashMapOf(0 to exp(-lambda)) +fun Distribution.Companion.poisson( + lambda: Double +): DiscreteSamplerDistribution = object : DiscreteSamplerDistribution() { - override fun buildSampler(generator: RandomGenerator): DiscreteSampler = - PoissonSampler.of(generator.asUniformRandomProvider(), lambda) + override fun buildSampler(generator: RandomGenerator): DiscreteSampler { + return PoissonSampler.of(generator.asUniformRandomProvider(), lambda) + } - override fun probability(arg: Int): Double { - require(arg >= 0) { "The argument must be >= 0" } + private val computedProb: HashMap = hashMapOf(0 to exp(-lambda)) - return if (arg > 40) - exp(-(arg - lambda).pow(2) / 2 / lambda) / sqrt(2 * PI * lambda) - else - computedProb.getOrPut(arg) { probability(arg - 1) * lambda / arg } + override fun probability(arg: Int): Double { + require(arg >= 0) { "The argument must be >= 0" } + return if (arg > 40) { + exp(-(arg - lambda).pow(2) / 2 / lambda) / sqrt(2 * PI * lambda) + } else { + computedProb.getOrPut(arg) { + probability(arg - 1) * lambda / arg + } } } +} diff --git a/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/CommonsDistributionsTest.kt b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/CommonsDistributionsTest.kt similarity index 91% rename from kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/CommonsDistributionsTest.kt rename to kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/CommonsDistributionsTest.kt index fe58fac08..7638c695e 100644 --- a/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/CommonsDistributionsTest.kt +++ b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/CommonsDistributionsTest.kt @@ -1,4 +1,4 @@ -package kscience.kmath.stat +package scientifik.kmath.prob import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList @@ -6,7 +6,7 @@ import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -internal class CommonsDistributionsTest { +class CommonsDistributionsTest { @Test fun testNormalDistributionSuspend() { val distribution = Distribution.normal(7.0, 2.0) @@ -24,4 +24,5 @@ internal class CommonsDistributionsTest { val sample = distribution.sample(generator).nextBlock(1000) Assertions.assertEquals(7.0, sample.average(), 0.1) } -} + +} \ No newline at end of file diff --git a/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/SamplerTest.kt b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/SamplerTest.kt similarity index 84% rename from kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/SamplerTest.kt rename to kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/SamplerTest.kt index afed4c5d0..1152f3057 100644 --- a/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/SamplerTest.kt +++ b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/SamplerTest.kt @@ -1,4 +1,4 @@ -package kscience.kmath.stat +package scientifik.kmath.prob import kotlinx.coroutines.runBlocking import kotlin.test.Test @@ -6,7 +6,7 @@ import kotlin.test.Test class SamplerTest { @Test - fun bufferSamplerTest() { + fun bufferSamplerTest(){ val sampler: Sampler = BasicSampler { it.chain { nextDouble() } } val data = sampler.sampleBuffer(RandomGenerator.default, 100) diff --git a/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/StatisticTest.kt b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt similarity index 81% rename from kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/StatisticTest.kt rename to kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt index 5cee4d172..2613f71d5 100644 --- a/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/StatisticTest.kt +++ b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt @@ -1,20 +1,18 @@ -package kscience.kmath.stat +package scientifik.kmath.prob import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking -import kscience.kmath.streaming.chunked +import scientifik.kmath.streaming.chunked import kotlin.test.Test -internal class StatisticTest { +class StatisticTest { //create a random number generator. val generator = RandomGenerator.default(1) - //Create a stateless chain from generator. val data = generator.chain { nextDouble() } - - //Convert a chain to Flow and break it into chunks. + //Convert a chaint to Flow and break it into chunks. val chunked = data.chunked(1000) @Test @@ -24,8 +22,7 @@ internal class StatisticTest { .flow(chunked) //create a flow with results .drop(99) // Skip first 99 values and use one with total data .first() //get 1e5 data samples average - println(average) } } -} +} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/FactorizedDistribution.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/FactorizedDistribution.kt deleted file mode 100644 index 1ed9deba9..000000000 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/FactorizedDistribution.kt +++ /dev/null @@ -1,43 +0,0 @@ -package kscience.kmath.stat - -import kscience.kmath.chains.Chain -import kscience.kmath.chains.SimpleChain - -/** - * A multivariate distribution which takes a map of parameters - */ -public interface NamedDistribution : Distribution> - -/** - * A multivariate distribution that has independent distributions for separate axis - */ -public class FactorizedDistribution(public val distributions: Collection>) : - NamedDistribution { - override fun probability(arg: Map): Double = - distributions.fold(1.0) { acc, distr -> acc * distr.probability(arg) } - - override fun sample(generator: RandomGenerator): Chain> { - val chains = distributions.map { it.sample(generator) } - return SimpleChain { chains.fold(emptyMap()) { acc, chain -> acc + chain.next() } } - } -} - -public class NamedDistributionWrapper(public val name: String, public val distribution: Distribution) : - NamedDistribution { - override fun probability(arg: Map): Double = distribution.probability( - arg[name] ?: error("Argument with name $name not found in input parameters") - ) - - override fun sample(generator: RandomGenerator): Chain> { - val chain = distribution.sample(generator) - return SimpleChain { mapOf(name to chain.next()) } - } -} - -public class DistributionBuilder { - private val distributions = ArrayList>() - - public infix fun String.to(distribution: Distribution) { - distributions.add(NamedDistributionWrapper(this, distribution)) - } -} diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/Fitting.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/Fitting.kt deleted file mode 100644 index 9d4655df2..000000000 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/Fitting.kt +++ /dev/null @@ -1,63 +0,0 @@ -package kscience.kmath.stat - -import kscience.kmath.expressions.* -import kscience.kmath.operations.ExtendedField -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.indices -import kotlin.math.pow - -public object Fitting { - - /** - * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation - */ - public fun chiSquared( - autoDiff: AutoDiffProcessor>, - x: Buffer, - y: Buffer, - yErr: Buffer, - model: A.(I) -> I, - ): DifferentiableExpression> where A : ExtendedField, A : ExpressionAlgebra { - require(x.size == y.size) { "X and y buffers should be of the same size" } - require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } - - return autoDiff.process { - var sum = zero - - x.indices.forEach { - val xValue = const(x[it]) - val yValue = const(y[it]) - val yErrValue = const(yErr[it]) - val modelValue = model(xValue) - sum += ((yValue - modelValue) / yErrValue).pow(2) - } - - sum - } - } - - /** - * Generate a chi squared expression from given x-y-sigma model represented by an expression. Does not provide derivatives - */ - public fun chiSquared( - x: Buffer, - y: Buffer, - yErr: Buffer, - model: Expression, - xSymbol: Symbol = StringSymbol("x"), - ): Expression { - require(x.size == y.size) { "X and y buffers should be of the same size" } - require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } - - return Expression { arguments -> - x.indices.sumByDouble { - val xValue = x[it] - val yValue = y[it] - val yErrValue = yErr[it] - val modifiedArgs = arguments + (xSymbol to xValue) - val modelValue = model(modifiedArgs) - ((yValue - modelValue) / yErrValue).pow(2) - } - } - } -} diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/MCScope.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/MCScope.kt deleted file mode 100644 index 5dc567db8..000000000 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/MCScope.kt +++ /dev/null @@ -1,58 +0,0 @@ -package kscience.kmath.stat - -import kotlinx.coroutines.* -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.coroutines.coroutineContext - -/** - * A scope for a Monte-Carlo computations or multi-coroutine random number generation. - * The scope preserves the order of random generator calls as long as all concurrency calls is done via [launch] and [async] - * functions. - */ -public class MCScope( - public val coroutineContext: CoroutineContext, - public val random: RandomGenerator, -) - -/** - * Launches a supervised Monte-Carlo scope - */ -public suspend inline fun mcScope(generator: RandomGenerator, block: MCScope.() -> T): T = - MCScope(coroutineContext, generator).block() - -/** - * Launch mc scope with a given seed - */ -public suspend inline fun mcScope(seed: Long, block: MCScope.() -> T): T = - mcScope(RandomGenerator.default(seed), block) - -/** - * Specialized launch for [MCScope]. Behaves the same way as regular [CoroutineScope.launch], but also stores the generator fork. - * The method itself is not thread safe. - */ -public inline fun MCScope.launch( - context: CoroutineContext = EmptyCoroutineContext, - start: CoroutineStart = CoroutineStart.DEFAULT, - crossinline block: suspend MCScope.() -> Unit, -): Job { - val newRandom = random.fork() - return CoroutineScope(coroutineContext).launch(context, start) { - MCScope(coroutineContext, newRandom).block() - } -} - -/** - * Specialized async for [MCScope]. Behaves the same way as regular [CoroutineScope.async], but also stores the generator fork. - * The method itself is not thread safe. - */ -public inline fun MCScope.async( - context: CoroutineContext = EmptyCoroutineContext, - start: CoroutineStart = CoroutineStart.DEFAULT, - crossinline block: suspend MCScope.() -> T, -): Deferred { - val newRandom = random.fork() - return CoroutineScope(coroutineContext).async(context, start) { - MCScope(coroutineContext, newRandom).block() - } -} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/OptimizationProblem.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/OptimizationProblem.kt deleted file mode 100644 index 0f3cd9dd9..000000000 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/OptimizationProblem.kt +++ /dev/null @@ -1,88 +0,0 @@ -package kscience.kmath.stat - -import kscience.kmath.expressions.DifferentiableExpression -import kscience.kmath.expressions.Expression -import kscience.kmath.expressions.Symbol - -public interface OptimizationFeature - -public class OptimizationResult( - public val point: Map, - public val value: T, - public val features: Set = emptySet(), -) { - override fun toString(): String { - return "OptimizationResult(point=$point, value=$value)" - } -} - -public operator fun OptimizationResult.plus( - feature: OptimizationFeature, -): OptimizationResult = OptimizationResult(point, value, features + feature) - -/** - * A configuration builder for optimization problem - */ -public interface OptimizationProblem { - /** - * Define the initial guess for the optimization problem - */ - public fun initialGuess(map: Map) - - /** - * Set an objective function expression - */ - public fun expression(expression: Expression) - - /** - * Set a differentiable expression as objective function as function and gradient provider - */ - public fun diffExpression(expression: DifferentiableExpression>) - - /** - * Update the problem from previous optimization run - */ - public fun update(result: OptimizationResult) - - /** - * Make an optimization run - */ - public fun optimize(): OptimizationResult -} - -public fun interface OptimizationProblemFactory> { - public fun build(symbols: List): P -} - -public operator fun > OptimizationProblemFactory.invoke( - symbols: List, - block: P.() -> Unit, -): P = build(symbols).apply(block) - -/** - * Optimize expression without derivatives using specific [OptimizationProblemFactory] - */ -public fun > Expression.optimizeWith( - factory: OptimizationProblemFactory, - vararg symbols: Symbol, - configuration: F.() -> Unit, -): OptimizationResult { - require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } - val problem = factory(symbols.toList(),configuration) - problem.expression(this) - return problem.optimize() -} - -/** - * Optimize differentiable expression using specific [OptimizationProblemFactory] - */ -public fun > DifferentiableExpression>.optimizeWith( - factory: OptimizationProblemFactory, - vararg symbols: Symbol, - configuration: F.() -> Unit, -): OptimizationResult { - require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } - val problem = factory(symbols.toList(), configuration) - problem.diffExpression(this) - return problem.optimize() -} diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/RandomChain.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/RandomChain.kt deleted file mode 100644 index 0f10851b9..000000000 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/RandomChain.kt +++ /dev/null @@ -1,17 +0,0 @@ -package kscience.kmath.stat - -import kscience.kmath.chains.Chain - -/** - * A possibly stateful chain producing random values. - */ -public class RandomChain( - public val generator: RandomGenerator, - private val gen: suspend RandomGenerator.() -> R -) : Chain { - override suspend fun next(): R = generator.gen() - - override fun fork(): Chain = RandomChain(generator.fork(), gen) -} - -public fun RandomGenerator.chain(gen: suspend RandomGenerator.() -> R): RandomChain = RandomChain(this, gen) diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/RandomGenerator.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/RandomGenerator.kt deleted file mode 100644 index 4486ae016..000000000 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/RandomGenerator.kt +++ /dev/null @@ -1,100 +0,0 @@ -package kscience.kmath.stat - -import kotlin.random.Random - -/** - * An interface that is implemented by random number generator algorithms. - */ -public interface RandomGenerator { - /** - * Gets the next random [Boolean] value. - */ - public fun nextBoolean(): Boolean - - /** - * Gets the next random [Double] value uniformly distributed between 0 (inclusive) and 1 (exclusive). - */ - public fun nextDouble(): Double - - /** - * Gets the next random `Int` from the random number generator. - * - * Generates an `Int` random value uniformly distributed between [Int.MIN_VALUE] and [Int.MAX_VALUE] (inclusive). - */ - public fun nextInt(): Int - - /** - * Gets the next random non-negative `Int` from the random number generator less than the specified [until] bound. - * - * Generates an `Int` random value uniformly distributed between `0` (inclusive) and the specified [until] bound - * (exclusive). - */ - public fun nextInt(until: Int): Int - - /** - * Gets the next random `Long` from the random number generator. - * - * Generates a `Long` random value uniformly distributed between [Long.MIN_VALUE] and [Long.MAX_VALUE] (inclusive). - */ - public fun nextLong(): Long - - /** - * Gets the next random non-negative `Long` from the random number generator less than the specified [until] bound. - * - * Generates a `Long` random value uniformly distributed between `0` (inclusive) and the specified [until] bound (exclusive). - */ - public fun nextLong(until: Long): Long - - /** - * Fills a subrange of the specified byte [array] starting from [fromIndex] inclusive and ending [toIndex] exclusive - * with random bytes. - * - * @return [array] with the subrange filled with random bytes. - */ - public fun fillBytes(array: ByteArray, fromIndex: Int = 0, toIndex: Int = array.size) - - /** - * Creates a byte array of the specified [size], filled with random bytes. - */ - public fun nextBytes(size: Int): ByteArray = ByteArray(size).also { fillBytes(it) } - - /** - * Create a new generator which is independent from current generator (operations on new generator do not affect this one - * and vise versa). The statistical properties of new generator should be the same as for this one. - * For pseudo-random generator, the fork is keeping the same sequence of numbers for given call order for each run. - * - * The thread safety of this operation is not guaranteed since it could affect the state of the generator. - */ - public fun fork(): RandomGenerator - - public companion object { - /** - * The [DefaultGenerator] instance. - */ - public val default: DefaultGenerator by lazy(::DefaultGenerator) - - /** - * Returns [DefaultGenerator] of given [seed]. - */ - public fun default(seed: Long): DefaultGenerator = DefaultGenerator(Random(seed)) - } -} - -/** - * Implements [RandomGenerator] by delegating all operations to [Random]. - */ -public inline class DefaultGenerator(public val random: Random = Random) : RandomGenerator { - public override fun nextBoolean(): Boolean = random.nextBoolean() - public override fun nextDouble(): Double = random.nextDouble() - public override fun nextInt(): Int = random.nextInt() - public override fun nextInt(until: Int): Int = random.nextInt(until) - public override fun nextLong(): Long = random.nextLong() - public override fun nextLong(until: Long): Long = random.nextLong(until) - - public override fun fillBytes(array: ByteArray, fromIndex: Int, toIndex: Int) { - random.nextBytes(array, fromIndex, toIndex) - } - - public override fun nextBytes(size: Int): ByteArray = random.nextBytes(size) - public override fun fork(): RandomGenerator = RandomGenerator.default(random.nextLong()) -} diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/SamplerAlgebra.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/SamplerAlgebra.kt deleted file mode 100644 index f416028a5..000000000 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/SamplerAlgebra.kt +++ /dev/null @@ -1,31 +0,0 @@ -package kscience.kmath.stat - -import kscience.kmath.chains.Chain -import kscience.kmath.chains.ConstantChain -import kscience.kmath.chains.map -import kscience.kmath.chains.zip -import kscience.kmath.operations.Space -import kscience.kmath.operations.invoke - -public class BasicSampler(public val chainBuilder: (RandomGenerator) -> Chain) : Sampler { - public override fun sample(generator: RandomGenerator): Chain = chainBuilder(generator) -} - -public class ConstantSampler(public val value: T) : Sampler { - public override fun sample(generator: RandomGenerator): Chain = ConstantChain(value) -} - -/** - * A space for samplers. Allows to perform simple operations on distributions - */ -public class SamplerSpace(public val space: Space) : Space> { - public override val zero: Sampler = ConstantSampler(space.zero) - - public override fun add(a: Sampler, b: Sampler): Sampler = BasicSampler { generator -> - a.sample(generator).zip(b.sample(generator)) { aValue, bValue -> space { aValue + bValue } } - } - - public override fun multiply(a: Sampler, k: Number): Sampler = BasicSampler { generator -> - a.sample(generator).map { space { it * k.toDouble() } } - } -} diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/Statistic.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/Statistic.kt deleted file mode 100644 index a4624fc21..000000000 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/Statistic.kt +++ /dev/null @@ -1,96 +0,0 @@ -package kscience.kmath.stat - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.runningReduce -import kscience.kmath.coroutines.mapParallel -import kscience.kmath.operations.* -import kscience.kmath.structures.Buffer -import kscience.kmath.structures.asIterable -import kscience.kmath.structures.asSequence - -/** - * A function, that transforms a buffer of random quantities to some resulting value - */ -public interface Statistic { - public suspend operator fun invoke(data: Buffer): R -} - -/** - * A statistic tha could be computed separately on different blocks of data and then composed - * @param T - source type - * @param I - intermediate block type - * @param R - result type - */ -public interface ComposableStatistic : Statistic { - //compute statistic on a single block - public suspend fun computeIntermediate(data: Buffer): I - - //Compose two blocks - public suspend fun composeIntermediate(first: I, second: I): I - - //Transform block to result - public suspend fun toResult(intermediate: I): R - - public override suspend fun invoke(data: Buffer): R = toResult(computeIntermediate(data)) -} - -@FlowPreview -@ExperimentalCoroutinesApi -private fun ComposableStatistic.flowIntermediate( - flow: Flow>, - dispatcher: CoroutineDispatcher = Dispatchers.Default -): Flow = flow - .mapParallel(dispatcher) { computeIntermediate(it) } - .runningReduce(::composeIntermediate) - - -/** - * Perform a streaming statistical analysis on a chunked data. The computation of inner representation is done in parallel - * if [dispatcher] allows it. - * - * The resulting flow contains values that include the whole previous statistics, not only the last chunk. - */ -@FlowPreview -@ExperimentalCoroutinesApi -public fun ComposableStatistic.flow( - flow: Flow>, - dispatcher: CoroutineDispatcher = Dispatchers.Default -): Flow = flowIntermediate(flow, dispatcher).map(::toResult) - -/** - * Arithmetic mean - */ -public class Mean(public val space: Space) : ComposableStatistic, T> { - public override suspend fun computeIntermediate(data: Buffer): Pair = - space { sum(data.asIterable()) } to data.size - - public override suspend fun composeIntermediate(first: Pair, second: Pair): Pair = - space { first.first + second.first } to (first.second + second.second) - - public override suspend fun toResult(intermediate: Pair): T = - space { intermediate.first / intermediate.second } - - public companion object { - //TODO replace with optimized version which respects overflow - public val real: Mean = Mean(RealField) - public val int: Mean = Mean(IntRing) - public val long: Mean = Mean(LongRing) - } -} - -/** - * Non-composable median - */ -public class Median(private val comparator: Comparator) : Statistic { - public override suspend fun invoke(data: Buffer): T = - data.asSequence().sortedWith(comparator).toList()[data.size / 2] //TODO check if this is correct - - public companion object { - public val real: Median = Median { a: Double, b: Double -> a.compareTo(b) } - } -} diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/UniformDistribution.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/UniformDistribution.kt deleted file mode 100644 index 1ba5c96f1..000000000 --- a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/UniformDistribution.kt +++ /dev/null @@ -1,22 +0,0 @@ -package kscience.kmath.stat - -import kscience.kmath.chains.Chain -import kscience.kmath.chains.SimpleChain - -public class UniformDistribution(public val range: ClosedFloatingPointRange) : UnivariateDistribution { - private val length: Double = range.endInclusive - range.start - - override fun probability(arg: Double): Double = if (arg in range) 1.0 / length else 0.0 - - override fun sample(generator: RandomGenerator): Chain = - SimpleChain { range.start + generator.nextDouble() * length } - - override fun cumulative(arg: Double): Double = when { - arg < range.start -> 0.0 - arg >= range.endInclusive -> 1.0 - else -> (arg - range.start) / length - } -} - -public fun Distribution.Companion.uniform(range: ClosedFloatingPointRange): UniformDistribution = - UniformDistribution(range) diff --git a/kmath-stat/src/jvmMain/kotlin/kscience/kmath/stat/RandomSourceGenerator.kt b/kmath-stat/src/jvmMain/kotlin/kscience/kmath/stat/RandomSourceGenerator.kt deleted file mode 100644 index 5cba28a95..000000000 --- a/kmath-stat/src/jvmMain/kotlin/kscience/kmath/stat/RandomSourceGenerator.kt +++ /dev/null @@ -1,58 +0,0 @@ -package kscience.kmath.stat - -import org.apache.commons.rng.UniformRandomProvider -import org.apache.commons.rng.simple.RandomSource - -public class RandomSourceGenerator(public val source: RandomSource, seed: Long?) : RandomGenerator { - internal val random: UniformRandomProvider = seed?.let { - RandomSource.create(source, seed) - } ?: RandomSource.create(source) - - public override fun nextBoolean(): Boolean = random.nextBoolean() - public override fun nextDouble(): Double = random.nextDouble() - public override fun nextInt(): Int = random.nextInt() - public override fun nextInt(until: Int): Int = random.nextInt(until) - public override fun nextLong(): Long = random.nextLong() - public override fun nextLong(until: Long): Long = random.nextLong(until) - - public override fun fillBytes(array: ByteArray, fromIndex: Int, toIndex: Int) { - require(toIndex > fromIndex) - random.nextBytes(array, fromIndex, toIndex - fromIndex) - } - - public override fun fork(): RandomGenerator = RandomSourceGenerator(source, nextLong()) -} - -public inline class RandomGeneratorProvider(public val generator: RandomGenerator) : UniformRandomProvider { - public override fun nextBoolean(): Boolean = generator.nextBoolean() - public override fun nextFloat(): Float = generator.nextDouble().toFloat() - - public override fun nextBytes(bytes: ByteArray) { - generator.fillBytes(bytes) - } - - public override fun nextBytes(bytes: ByteArray, start: Int, len: Int) { - generator.fillBytes(bytes, start, start + len) - } - - public override fun nextInt(): Int = generator.nextInt() - public override fun nextInt(n: Int): Int = generator.nextInt(n) - public override fun nextDouble(): Double = generator.nextDouble() - public override fun nextLong(): Long = generator.nextLong() - public override fun nextLong(n: Long): Long = generator.nextLong(n) -} - -/** - * Represent this [RandomGenerator] as commons-rng [UniformRandomProvider] preserving and mirroring its current state. - * Getting new value from one of those changes the state of another. - */ -public fun RandomGenerator.asUniformRandomProvider(): UniformRandomProvider = if (this is RandomSourceGenerator) - random -else - RandomGeneratorProvider(this) - -public fun RandomGenerator.Companion.fromSource(source: RandomSource, seed: Long? = null): RandomSourceGenerator = - RandomSourceGenerator(source, seed) - -public fun RandomGenerator.Companion.mersenneTwister(seed: Long? = null): RandomSourceGenerator = - fromSource(RandomSource.MT, seed) diff --git a/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/MCScopeTest.kt b/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/MCScopeTest.kt deleted file mode 100644 index c2304070f..000000000 --- a/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/MCScopeTest.kt +++ /dev/null @@ -1,85 +0,0 @@ -package kscience.kmath.stat - -import kotlinx.coroutines.* -import java.util.* -import kotlin.collections.HashSet -import kotlin.test.Test -import kotlin.test.assertEquals - -data class RandomResult(val branch: String, val order: Int, val value: Int) - -typealias ATest = suspend CoroutineScope.() -> Set - -class MCScopeTest { - val simpleTest: ATest = { - mcScope(1111) { - val res = Collections.synchronizedSet(HashSet()) - - launch { - //println(random) - repeat(10) { - delay(10) - res.add(RandomResult("first", it, random.nextInt())) - } - launch { - //empty fork - } - } - - launch { - //println(random) - repeat(10) { - delay(10) - res.add(RandomResult("second", it, random.nextInt())) - } - } - - - res - } - } - - val testWithJoin: ATest = { - mcScope(1111) { - val res = Collections.synchronizedSet(HashSet()) - - val job = launch { - repeat(10) { - delay(10) - res.add(RandomResult("first", it, random.nextInt())) - } - } - launch { - repeat(10) { - delay(10) - if (it == 4) job.join() - res.add(RandomResult("second", it, random.nextInt())) - } - } - - res - } - } - - - fun compareResult(test: ATest) { - val res1 = runBlocking(Dispatchers.Default) { test() } - val res2 = runBlocking(newSingleThreadContext("test")) { test() } - assertEquals( - res1.find { it.branch == "first" && it.order == 7 }?.value, - res2.find { it.branch == "first" && it.order == 7 }?.value - ) - assertEquals(res1, res2) - } - - @Test - fun testParallel() { - compareResult(simpleTest) - } - - - @Test - fun testConditionalJoin() { - compareResult(testWithJoin) - } -} \ No newline at end of file diff --git a/kmath-viktor/build.gradle.kts b/kmath-viktor/build.gradle.kts index 3e5c5912c..52ee7c497 100644 --- a/kmath-viktor/build.gradle.kts +++ b/kmath-viktor/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("ru.mipt.npm.jvm") + id("scientifik.jvm") } description = "Binding for https://github.com/JetBrains-Research/viktor" @@ -7,4 +7,4 @@ description = "Binding for https://github.com/JetBrains-Research/viktor" dependencies { api(project(":kmath-core")) api("org.jetbrains.bio:viktor:1.0.1") -} +} \ No newline at end of file diff --git a/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorBuffer.kt b/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorBuffer.kt deleted file mode 100644 index 5c9611758..000000000 --- a/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorBuffer.kt +++ /dev/null @@ -1,19 +0,0 @@ -package kscience.kmath.viktor - -import kscience.kmath.structures.MutableBuffer -import org.jetbrains.bio.viktor.F64FlatArray - -@Suppress("NOTHING_TO_INLINE", "OVERRIDE_BY_INLINE") -public inline class ViktorBuffer(public val flatArray: F64FlatArray) : MutableBuffer { - public override val size: Int - get() = flatArray.size - - public override inline fun get(index: Int): Double = flatArray[index] - - public override inline fun set(index: Int, value: Double) { - flatArray[index] = value - } - - public override fun copy(): MutableBuffer = ViktorBuffer(flatArray.copy().flatten()) - public override operator fun iterator(): Iterator = flatArray.data.iterator() -} diff --git a/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt b/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt deleted file mode 100644 index 2471362cb..000000000 --- a/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt +++ /dev/null @@ -1,88 +0,0 @@ -package kscience.kmath.viktor - -import kscience.kmath.operations.RealField -import kscience.kmath.structures.DefaultStrides -import kscience.kmath.structures.MutableNDStructure -import kscience.kmath.structures.NDField -import kscience.kmath.structures.Strides -import org.jetbrains.bio.viktor.F64Array - -@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public inline class ViktorNDStructure(public val f64Buffer: F64Array) : MutableNDStructure { - public override val shape: IntArray get() = f64Buffer.shape - - public override inline fun get(index: IntArray): Double = f64Buffer.get(*index) - - public override inline fun set(index: IntArray, value: Double) { - f64Buffer.set(*index, value = value) - } - - public override fun elements(): Sequence> = - DefaultStrides(shape).indices().map { it to get(it) } -} - -public fun F64Array.asStructure(): ViktorNDStructure = ViktorNDStructure(this) - -@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public class ViktorNDField(public override val shape: IntArray) : NDField { - public override val zero: ViktorNDStructure - get() = F64Array.full(init = 0.0, shape = shape).asStructure() - - public override val one: ViktorNDStructure - get() = F64Array.full(init = 1.0, shape = shape).asStructure() - - public val strides: Strides = DefaultStrides(shape) - - public override val elementContext: RealField get() = RealField - - public override fun produce(initializer: RealField.(IntArray) -> Double): ViktorNDStructure = - F64Array(*shape).apply { - this@ViktorNDField.strides.indices().forEach { index -> - set(value = RealField.initializer(index), indices = index) - } - }.asStructure() - - public override fun map(arg: ViktorNDStructure, transform: RealField.(Double) -> Double): ViktorNDStructure = - F64Array(*shape).apply { - this@ViktorNDField.strides.indices().forEach { index -> - set(value = RealField.transform(arg[index]), indices = index) - } - }.asStructure() - - public override fun mapIndexed( - arg: ViktorNDStructure, - transform: RealField.(index: IntArray, Double) -> Double - ): ViktorNDStructure = F64Array(*shape).apply { - this@ViktorNDField.strides.indices().forEach { index -> - set(value = RealField.transform(index, arg[index]), indices = index) - } - }.asStructure() - - public override fun combine( - a: ViktorNDStructure, - b: ViktorNDStructure, - transform: RealField.(Double, Double) -> Double - ): ViktorNDStructure = F64Array(*shape).apply { - this@ViktorNDField.strides.indices().forEach { index -> - set(value = RealField.transform(a[index], b[index]), indices = index) - } - }.asStructure() - - public override fun add(a: ViktorNDStructure, b: ViktorNDStructure): ViktorNDStructure = - (a.f64Buffer + b.f64Buffer).asStructure() - - public override fun multiply(a: ViktorNDStructure, k: Number): ViktorNDStructure = - (a.f64Buffer * k.toDouble()).asStructure() - - public override inline fun ViktorNDStructure.plus(b: ViktorNDStructure): ViktorNDStructure = - (f64Buffer + b.f64Buffer).asStructure() - - public override inline fun ViktorNDStructure.minus(b: ViktorNDStructure): ViktorNDStructure = - (f64Buffer - b.f64Buffer).asStructure() - - public override inline fun ViktorNDStructure.times(k: Number): ViktorNDStructure = - (f64Buffer * k.toDouble()).asStructure() - - public override inline fun ViktorNDStructure.plus(arg: Double): ViktorNDStructure = - (f64Buffer.plus(arg)).asStructure() -} \ No newline at end of file diff --git a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorBuffer.kt b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorBuffer.kt new file mode 100644 index 000000000..040eee951 --- /dev/null +++ b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorBuffer.kt @@ -0,0 +1,20 @@ +package scientifik.kmath.viktor + +import org.jetbrains.bio.viktor.F64FlatArray +import scientifik.kmath.structures.MutableBuffer + +@Suppress("NOTHING_TO_INLINE", "OVERRIDE_BY_INLINE") +inline class ViktorBuffer(val flatArray: F64FlatArray) : MutableBuffer { + override val size: Int get() = flatArray.size + + override inline fun get(index: Int): Double = flatArray[index] + override inline fun set(index: Int, value: Double) { + flatArray[index] = value + } + + override fun copy(): MutableBuffer { + return ViktorBuffer(flatArray.copy().flatten()) + } + + override fun iterator(): Iterator = flatArray.data.iterator() +} \ No newline at end of file diff --git a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt new file mode 100644 index 000000000..84e927721 --- /dev/null +++ b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt @@ -0,0 +1,86 @@ +package scientifik.kmath.viktor + +import org.jetbrains.bio.viktor.F64Array +import scientifik.kmath.operations.RealField +import scientifik.kmath.structures.DefaultStrides +import scientifik.kmath.structures.MutableNDStructure +import scientifik.kmath.structures.NDField + +@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") +inline class ViktorNDStructure(val f64Buffer: F64Array) : MutableNDStructure { + + override val shape: IntArray get() = f64Buffer.shape + + override inline fun get(index: IntArray): Double = f64Buffer.get(*index) + + override inline fun set(index: IntArray, value: Double) { + f64Buffer.set(*index, value = value) + } + + override fun elements(): Sequence> { + return DefaultStrides(shape).indices().map { it to get(it) } + } +} + +fun F64Array.asStructure(): ViktorNDStructure = ViktorNDStructure(this) + +@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") +class ViktorNDField(override val shape: IntArray) : NDField { + override val zero: ViktorNDStructure + get() = F64Array.full(init = 0.0, shape = *shape).asStructure() + override val one: ViktorNDStructure + get() = F64Array.full(init = 1.0, shape = *shape).asStructure() + + val strides = DefaultStrides(shape) + + override val elementContext: RealField get() = RealField + + override fun produce(initializer: RealField.(IntArray) -> Double): ViktorNDStructure = F64Array(*shape).apply { + this@ViktorNDField.strides.indices().forEach { index -> + set(value = RealField.initializer(index), indices = *index) + } + }.asStructure() + + override fun map(arg: ViktorNDStructure, transform: RealField.(Double) -> Double): ViktorNDStructure = + F64Array(*shape).apply { + this@ViktorNDField.strides.indices().forEach { index -> + set(value = RealField.transform(arg[index]), indices = *index) + } + }.asStructure() + + override fun mapIndexed( + arg: ViktorNDStructure, + transform: RealField.(index: IntArray, Double) -> Double + ): ViktorNDStructure = F64Array(*shape).apply { + this@ViktorNDField.strides.indices().forEach { index -> + set(value = RealField.transform(index, arg[index]), indices = *index) + } + }.asStructure() + + override fun combine( + a: ViktorNDStructure, + b: ViktorNDStructure, + transform: RealField.(Double, Double) -> Double + ): ViktorNDStructure = F64Array(*shape).apply { + this@ViktorNDField.strides.indices().forEach { index -> + set(value = RealField.transform(a[index], b[index]), indices = *index) + } + }.asStructure() + + override fun add(a: ViktorNDStructure, b: ViktorNDStructure): ViktorNDStructure { + return (a.f64Buffer + b.f64Buffer).asStructure() + } + + override fun multiply(a: ViktorNDStructure, k: Number): ViktorNDStructure = + (a.f64Buffer * k.toDouble()).asStructure() + + override inline fun ViktorNDStructure.plus(b: ViktorNDStructure): ViktorNDStructure = + (f64Buffer + b.f64Buffer).asStructure() + + override inline fun ViktorNDStructure.minus(b: ViktorNDStructure): ViktorNDStructure = + (f64Buffer - b.f64Buffer).asStructure() + + override inline fun ViktorNDStructure.times(k: Number): ViktorNDStructure = (f64Buffer * k.toDouble()).asStructure() + + override inline fun ViktorNDStructure.plus(arg: Double): ViktorNDStructure = (f64Buffer.plus(arg)).asStructure() +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 10e4d9577..487e1d87f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,44 +1,51 @@ pluginManagement { + + val toolsVersion = "0.5.0" + + plugins { + id("kotlinx.benchmark") version "0.2.0-dev-8" + id("scientifik.mpp") version toolsVersion + id("scientifik.jvm") version toolsVersion + id("scientifik.atomic") version toolsVersion + id("scientifik.publish") version toolsVersion + kotlin("plugin.allopen") version "1.3.72" + } + repositories { - gradlePluginPortal() + mavenLocal() jcenter() + gradlePluginPortal() maven("https://dl.bintray.com/kotlin/kotlin-eap") - maven("https://dl.bintray.com/mipt-npm/kscience") + maven("https://dl.bintray.com/mipt-npm/scientifik") maven("https://dl.bintray.com/mipt-npm/dev") maven("https://dl.bintray.com/kotlin/kotlinx") } - val toolsVersion = "0.7.0" - val kotlinVersion = "1.4.20" - - plugins { - id("kotlinx.benchmark") version "0.2.0-dev-20" - id("ru.mipt.npm.project") version toolsVersion - id("ru.mipt.npm.mpp") version toolsVersion - id("ru.mipt.npm.jvm") version toolsVersion - id("ru.mipt.npm.publish") version toolsVersion - kotlin("jvm") version kotlinVersion - kotlin("plugin.allopen") version kotlinVersion + resolutionStrategy { + eachPlugin { + when (requested.id.id) { + "scientifik.mpp", "scientifik.jvm", "scientifik.publish" -> useModule("scientifik:gradle-tools:$toolsVersion") + } + } } } rootProject.name = "kmath" - include( ":kmath-memory", ":kmath-core", ":kmath-functions", +// ":kmath-io", ":kmath-coroutines", ":kmath-histograms", ":kmath-commons", ":kmath-viktor", - ":kmath-stat", - ":kmath-nd4j", + ":kmath-koma", + ":kmath-prob", + ":kmath-io", ":kmath-dimensions", ":kmath-for-real", ":kmath-geometry", ":kmath-ast", - ":kmath-ejml", - ":kmath-kotlingrad", ":examples" )