All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.rtron.math.analysis.function.univariate.combination.ConcatenatedFunction.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019-2022 Chair of Geoinformatics, Technical University of Munich
 *
 * 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 io.rtron.math.analysis.function.univariate.combination

import com.github.kittinunf.result.Result
import io.rtron.math.analysis.function.univariate.UnivariateFunction
import io.rtron.math.analysis.function.univariate.pure.ConstantFunction
import io.rtron.math.analysis.function.univariate.pure.LinearFunction
import io.rtron.math.analysis.function.univariate.pure.PolynomialFunction
import io.rtron.math.container.ConcatenationContainer
import io.rtron.math.range.Range
import io.rtron.std.handleFailure
import io.rtron.std.hasSameSizeAs
import io.rtron.std.isSorted
import io.rtron.std.isStrictlySorted

/**
 * Represents the sequential concatenation of the provided member functions.
 *
 * @param memberFunctions functions to be concatenated
 * @param absoluteStarts absolute start of the first function
 */
class ConcatenatedFunction(
    memberFunctions: List,
    absoluteDomains: List>,
    absoluteStarts: List
) : UnivariateFunction() {

    // Properties and Initializers
    private val container = ConcatenationContainer(memberFunctions, absoluteDomains, absoluteStarts)
    override val domain: Range get() = container.domain

    // Methods
    override fun valueUnbounded(x: Double): Result {
        val localMember = container.strictSelectMember(x)
            .handleFailure { return it }
        return localMember.member.valueUnbounded(localMember.localParameter)
    }

    override fun slopeUnbounded(x: Double): Result {
        val localMember = container.strictSelectMember(x)
            .handleFailure { return it }
        return localMember.member.slopeUnbounded(localMember.localParameter)
    }

    override fun valueInFuzzy(x: Double, tolerance: Double): Result {
        val localMember = container.fuzzySelectMember(x, tolerance)
            .handleFailure { return it }
        return localMember.member.valueUnbounded(localMember.localParameter)
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is ConcatenatedFunction) return false

        if (container != other.container) return false

        return true
    }

    override fun hashCode(): Int {
        return container.hashCode()
    }

    companion object {

        /**
         * Creates a concatenated function of a list of linear functions, whereby the slopes are adjusted so that the
         * concatenated function is continuous.
         * For example:
         * f(x) = slope_1 * x + 0 for [0, 5)
         * f(x) = slope_2 * x - 5 for [5, ∞)
         * The [starts] would be listOf(0, 5) and the [intercepts] would be listOf(0, -5).
         *
         * @param starts absolute start value of the function member
         * @param intercepts local intercept of the linear function
         * @param prependConstant if true, the first linear function is preceded by a constant function
         * @param appendConstant if true, the last linear function is appended by a constant function
         */
        fun ofLinearFunctions(
            starts: List,
            intercepts: List,
            prependConstant: Boolean = false,
            appendConstant: Boolean = true
        ): UnivariateFunction {
            require(starts.isNotEmpty() && intercepts.isNotEmpty()) { "List of starts and intercepts must not be empty." }
            require(starts.hasSameSizeAs(intercepts)) { "Equally sized starts and intercepts required." }
            require(starts.isSorted()) { "Start values must be sorted in ascending order." }

            // calculate slopes for continuous function
            val deltaIntercepts = intercepts.zipWithNext().map { it.second - it.first }
            val lengths = starts.zipWithNext().map { it.second - it.first }
            val slopes = deltaIntercepts.zip(lengths).map { it.first / it.second }

            // prepare linear functions
            val preparedStarts = starts.dropLast(1)
            val preparedLinearFunctions = slopes.zip(intercepts)
                .map { LinearFunction(it.first, it.second) }
            val preparedAbsoluteDomains = starts
                .zipWithNext()
                .map { Range.closedOpen(it.first, it.second) }

            // prepend function, if necessary
            val prependedStart = if (prependConstant)
                listOf(Double.MIN_VALUE) else emptyList()
            val prependedFunction = if (prependConstant)
                listOf(ConstantFunction(intercepts.first())) else emptyList()
            val prependedAbsoluteDomain = if (prependConstant)
                listOf(Range.lessThan(starts.first())) else emptyList()
            // append function, if necessary
            val appendedStart = if (appendConstant)
                listOf(starts.last()) else emptyList()
            val appendedFunction = if (appendConstant)
                listOf(ConstantFunction(intercepts.last())) else emptyList()
            val appendedAbsoluteDomain = if (appendConstant)
                listOf(Range.atLeast(starts.last())) else emptyList()

            return ConcatenatedFunction(
                prependedFunction + preparedLinearFunctions + appendedFunction,
                prependedAbsoluteDomain + preparedAbsoluteDomains + appendedAbsoluteDomain,
                prependedStart + preparedStarts + appendedStart
            )
        }

        /**
         * Creates a concatenated function with a list of polynomial function.
         * For example:
         * f(x) = 2 + 3*x + 4*x^2 + x^3 for [-2, 3)
         * f(x) = 1 + 2*x + 3*x^2 + 4* x^3  for [3, ∞)
         * The [starts] would be listOf(-2, 3) and the [coefficients] would be
         * listOf(arrayOf(2, 3, 4, 1), arrayOf(1, 2, 3, 4)).
         *
         * @param starts absolute start value of the function member
         * @param coefficients coefficients of the polynomial function members
         * @param prependConstant if true, the first linear function is preceded by a constant function
         */
        fun ofPolynomialFunctions(
            starts: List,
            coefficients: List,
            prependConstant: Boolean = false,
            prependConstantValue: Double = Double.NaN
        ): UnivariateFunction {

            require(starts.isNotEmpty() && coefficients.isNotEmpty()) { "List of starts and coefficients must not be empty." }
            require(starts.hasSameSizeAs(coefficients)) { "Equally sized starts and coefficients required." }
            require(starts.isStrictlySorted()) { "Polynomials must be sorted in strict ascending order." }

            // prepare polynomial functions and domains
            val polynomialFunctions = coefficients
                .map { PolynomialFunction(it) }
            val absoluteDomains = starts
                .zipWithNext()
                .map { Range.closedOpen(it.first, it.second) } + Range.atLeast(starts.last())

            // prepend function, if necessary
            val prependedStart = if (prependConstant)
                listOf(Double.MIN_VALUE) else emptyList()
            val prependedFunction = if (prependConstant) {
                val prependValue = if (prependConstantValue.isFinite()) prependConstantValue else
                    polynomialFunctions.first().value(0.0).handleFailure { throw it.error }

                listOf(ConstantFunction(prependValue))
            } else emptyList()
            val prependedAbsoluteDomain = if (prependConstant)
                listOf(Range.lessThan(starts.first())) else emptyList()

            return ConcatenatedFunction(
                prependedFunction + polynomialFunctions,
                prependedAbsoluteDomain + absoluteDomains,
                prependedStart + starts
            )
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy