Add binary and unary operations to RightContinuousStepFunctions

This commit is contained in:
Joel Therrien 2019-07-18 15:14:01 -07:00
parent d7cdc9f6e7
commit ae9a6b9a3f
5 changed files with 260 additions and 268 deletions

View file

@ -1,113 +0,0 @@
/*
* Copyright (c) 2019 Joel Therrien.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ca.joeltherrien.randomforest.utils;
/**
* Represents a function represented by discrete points. However, the function may be right-continuous or left-continuous
* at a given point, with no consistency. This function tracks that.
*/
public final class DiscontinuousStepFunction extends StepFunction {
private final double[] y;
private final boolean[] isLeftContinuous;
/**
* Represents the value that should be returned by evaluate if there are points prior to the time the function is being evaluated at.
*
* Map be null.
*/
private final double defaultY;
public DiscontinuousStepFunction(double[] x, double[] y, boolean[] isLeftContinuous, double defaultY) {
super(x);
this.y = y;
this.isLeftContinuous = isLeftContinuous;
this.defaultY = defaultY;
}
@Override
public double evaluate(double time){
int index = Utils.binarySearchLessThan(0, x.length, x, time);
if(index < 0){
return defaultY;
}
else{
if(x[index] == time){
return evaluateByIndex(index);
}
else{
return y[index];
}
}
}
@Override
public double evaluatePrevious(double time){
int index = Utils.binarySearchLessThan(0, x.length, x, time) - 1;
if(index < 0){
return defaultY;
}
else{
if(x[index] == time){
return evaluateByIndex(index);
}
else{
return y[index];
}
}
}
@Override
public double evaluateByIndex(int i) {
if(isLeftContinuous[i]){
i -= 1;
}
if(i < 0){
return defaultY;
}
return y[i];
}
@Override
public String toString(){
final StringBuilder builder = new StringBuilder();
builder.append("Default point: ");
builder.append(defaultY);
builder.append("\n");
for(int i=0; i<x.length; i++){
builder.append("x:");
builder.append(x[i]);
builder.append('\t');
if(isLeftContinuous[i]){
builder.append("*y:");
}
else{
builder.append("y*:");
}
builder.append(y[i]);
builder.append("\n");
}
return builder.toString();
}
}

View file

@ -1,129 +0,0 @@
/*
* Copyright (c) 2019 Joel Therrien.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ca.joeltherrien.randomforest.utils;
import java.util.List;
import java.util.ListIterator;
/**
* Represents a function represented by discrete points. We assume that the function is a stepwise left-continuous
* function, constant at the value of the previous encountered point.
*
*/
public final class LeftContinuousStepFunction extends StepFunction {
private final double[] y;
/**
* Represents the value that should be returned by evaluate if there are points prior to the time the function is being evaluated at.
*
* Map be null.
*/
private final double defaultY;
public LeftContinuousStepFunction(double[] x, double[] y, double defaultY) {
super(x);
this.y = y;
this.defaultY = defaultY;
}
/**
* This isn't a formal constructor because of limitations with abstract classes.
*
* @param pointList
* @param defaultY
* @return
*/
public static LeftContinuousStepFunction constructFromPoints(final List<Point> pointList, final double defaultY){
final double[] x = new double[pointList.size()];
final double[] y = new double[pointList.size()];
final ListIterator<Point> pointIterator = pointList.listIterator();
while(pointIterator.hasNext()){
final int index = pointIterator.nextIndex();
final Point currentPoint = pointIterator.next();
x[index] = currentPoint.getTime();
y[index] = currentPoint.getY();
}
return new LeftContinuousStepFunction(x, y, defaultY);
}
@Override
public double evaluate(double time){
int index = Utils.binarySearchLessThan(0, x.length, x, time);
if(index < 0){
return defaultY;
}
else{
if(x[index] == time){
return evaluateByIndex(index-1);
}
else{
return y[index];
}
}
}
@Override
public double evaluatePrevious(double time){
int index = Utils.binarySearchLessThan(0, x.length, x, time) - 1;
if(index < 0){
return defaultY;
}
else{
if(x[index] == time){
return evaluateByIndex(index-1);
}
else{
return y[index];
}
}
}
@Override
public double evaluateByIndex(int i) {
if(i < 0){
return defaultY;
}
return y[i];
}
@Override
public String toString(){
final StringBuilder builder = new StringBuilder();
builder.append("Default point: ");
builder.append(defaultY);
builder.append("\n");
for(int i=0; i<x.length; i++){
builder.append("x:");
builder.append(x[i]);
builder.append("\ty:");
builder.append(y[i]);
builder.append("\n");
}
return builder.toString();
}
}

View file

@ -16,8 +16,15 @@
package ca.joeltherrien.randomforest.utils; package ca.joeltherrien.randomforest.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.function.DoubleBinaryOperator;
import java.util.function.DoubleUnaryOperator;
import java.util.function.ToDoubleBiFunction;
import java.util.stream.DoubleStream;
import java.util.stream.Stream;
/** /**
* Represents a function represented by discrete points. We assume that the function is a stepwise right-continuous * Represents a function represented by discrete points. We assume that the function is a stepwise right-continuous
@ -29,9 +36,9 @@ public final class RightContinuousStepFunction extends StepFunction {
private final double[] y; private final double[] y;
/** /**
* Represents the value that should be returned by evaluate if there are points prior to the time the function is being evaluated at. * Represents the value that should be returned by evaluate if there are no points prior to the time the function is being evaluated at.
* *
* Map be null. * May not be null.
*/ */
private final double defaultY; private final double defaultY;
@ -186,5 +193,76 @@ public final class RightContinuousStepFunction extends StepFunction {
} }
public RightContinuousStepFunction unaryOperation(DoubleUnaryOperator operator){
final double newDefaultY = operator.applyAsDouble(this.defaultY);
final double[] newY = Arrays.stream(this.getY()).map(operator).toArray();
return new RightContinuousStepFunction(this.getX(), newY, newDefaultY);
}
public static RightContinuousStepFunction biOperation(RightContinuousStepFunction funLeft,
RightContinuousStepFunction funRight,
DoubleBinaryOperator operator){
final double newDefaultY = operator.applyAsDouble(funLeft.defaultY, funRight.defaultY);
final double[] leftX = funLeft.x;
final double[] rightX = funRight.x;
final List<Point> combinedPoints = new ArrayList<>(leftX.length + rightX.length);
// These indexes represent the times that have *already* been processed.
// They start at -1 because we already processed the defaultY values.
int indexLeft = -1;
int indexRight = -1;
// This while-loop will keep going until one of the functions reaches the ends of its points
while(indexLeft < leftX.length-1 && indexRight < rightX.length-1){
final double time;
if(leftX[indexLeft+1] < rightX[indexRight+1]){
indexLeft += 1;
time = leftX[indexLeft];
combinedPoints.add(new Point(time, operator.applyAsDouble(funLeft.evaluateByIndex(indexLeft), funRight.evaluateByIndex(indexRight))));
}
else if(leftX[indexLeft+1] > rightX[indexRight+1]){
indexRight += 1;
time = rightX[indexRight];
combinedPoints.add(new Point(time, operator.applyAsDouble(funLeft.evaluateByIndex(indexLeft), funRight.evaluateByIndex(indexRight))));
}
else{ // equal times
indexLeft += 1;
indexRight += 1;
time = leftX[indexLeft];
combinedPoints.add(new Point(time, operator.applyAsDouble(funLeft.evaluateByIndex(indexLeft), funRight.evaluateByIndex(indexRight))));
}
}
// At this point, at least one of function left or function right has reached the end of its points
// This while-loop occurring implies that functionRight can not move forward anymore
while(indexLeft < leftX.length-1){
indexLeft += 1;
final double time = leftX[indexLeft];
combinedPoints.add(new Point(time, operator.applyAsDouble(funLeft.evaluateByIndex(indexLeft), funRight.evaluateByIndex(indexRight))));
}
// This while-loop occurring implies that functionLeft can not move forward anymore
while(indexRight < rightX.length-1){
indexRight += 1;
final double time = rightX[indexRight];
combinedPoints.add(new Point(time, operator.applyAsDouble(funLeft.evaluateByIndex(indexLeft), funRight.evaluateByIndex(indexRight))));
}
return RightContinuousStepFunction.constructFromPoints(combinedPoints, newDefaultY);
}
} }

View file

@ -16,7 +16,6 @@
package ca.joeltherrien.randomforest.competingrisk; package ca.joeltherrien.randomforest.competingrisk;
import ca.joeltherrien.randomforest.utils.LeftContinuousStepFunction;
import ca.joeltherrien.randomforest.utils.RightContinuousStepFunction; import ca.joeltherrien.randomforest.utils.RightContinuousStepFunction;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -31,13 +30,6 @@ public class TestMathFunctions {
return new RightContinuousStepFunction(time, y, 0.1); return new RightContinuousStepFunction(time, y, 0.1);
} }
private LeftContinuousStepFunction generateLeftContinuousStepFunction(){
final double[] time = new double[]{1.0, 2.0, 3.0};
final double[] y = new double[]{-1.0, 1.0, 0.5};
return new LeftContinuousStepFunction(time, y, 0.1);
}
@Test @Test
public void testRightContinuousStepFunction(){ public void testRightContinuousStepFunction(){
final RightContinuousStepFunction function = generateRightContinuousStepFunction(); final RightContinuousStepFunction function = generateRightContinuousStepFunction();
@ -56,21 +48,5 @@ public class TestMathFunctions {
} }
@Test
public void testLeftContinuousStepFunction(){
final LeftContinuousStepFunction function = generateLeftContinuousStepFunction();
assertEquals(0.1, function.evaluate(0.5));
assertEquals(0.1, function.evaluate(1.0));
assertEquals(-1.0, function.evaluate(2.0));
assertEquals(1.0, function.evaluate(3.0));
assertEquals(0.1, function.evaluate(0.6));
assertEquals(-1.0, function.evaluate(1.1));
assertEquals(1.0, function.evaluate(2.1));
assertEquals(0.5, function.evaluate(3.1));
}
} }

View file

@ -0,0 +1,180 @@
package ca.joeltherrien.randomforest.utils;
import org.junit.jupiter.api.Test;
import java.util.function.DoubleBinaryOperator;
import java.util.function.DoubleUnaryOperator;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class RightContinuousStepFunctionOperatorTests {
// Idea - small and middle slightly overlap; middle and large slightly overlap.
// small and large never overlap (i.e. small's x values always occur before large's)
private final RightContinuousStepFunction smallNumbers;
private final RightContinuousStepFunction middleNumbers;
private final RightContinuousStepFunction largeNumbers;
private final double delta = 0.0000000001;
public RightContinuousStepFunctionOperatorTests(){
smallNumbers = RightContinuousStepFunction.constructFromPoints(Utils.easyList(
new Point(1.0, 1.0),
new Point(2.0, 3.0),
new Point(3.0, 2.0),
new Point(4.0, 1.0)
), 0.0);
middleNumbers = RightContinuousStepFunction.constructFromPoints(Utils.easyList(
new Point(3.5, 4.0),
new Point(4.0, 3.0),
new Point(5.0, 2.0),
new Point(6.0, 1.0)
), 5.0);
largeNumbers = RightContinuousStepFunction.constructFromPoints(Utils.easyList(
new Point(5.0, 5.0),
new Point(6.0, 6.0),
new Point(7.0, 3.0),
new Point(8.0, 2.0)
), 3.0);
}
@Test
public void testDifferenceNoOverlapLargeMinusSmall(){
DoubleBinaryOperator operator = (a, b) -> a - b;
final RightContinuousStepFunction largeSmallDifference = RightContinuousStepFunction.biOperation(
largeNumbers,
smallNumbers,
operator);
assertEquals(8, largeSmallDifference.getX().length);
assertEquals(8, largeSmallDifference.getY().length);
final double[] offsetTimes = {-0.1, 0.0, 0.1};
for(int time = 1; time <= 9; time++){
for(double offsetTime : offsetTimes){
final double timeToEvaluateAt = (double) time + offsetTime;
final double largeFunEvaluation = largeNumbers.evaluate(timeToEvaluateAt);
final double smallFunEvaluation = smallNumbers.evaluate(timeToEvaluateAt);
final double expectedDifference = operator.applyAsDouble(largeFunEvaluation, smallFunEvaluation);
final double actualEvaluation = largeSmallDifference.evaluate(timeToEvaluateAt);
assertEquals(expectedDifference, actualEvaluation, delta);
}
}
}
@Test
public void testDifferenceNoOverlapSmallMinusLarge(){
DoubleBinaryOperator operator = (a, b) -> a - b;
final RightContinuousStepFunction smallLargeDifference = RightContinuousStepFunction.biOperation(
smallNumbers,
largeNumbers,
operator);
assertEquals(8, smallLargeDifference.getX().length);
assertEquals(8, smallLargeDifference.getY().length);
final double[] offsetTimes = {-0.1, 0.0, 0.1};
for(int time = 1; time <= 9; time++){
for(double offsetTime : offsetTimes){
final double timeToEvaluateAt = (double) time + offsetTime;
final double smallFunEvaluation = smallNumbers.evaluate(timeToEvaluateAt);
final double largeFunEvaluation = largeNumbers.evaluate(timeToEvaluateAt);
final double expectedDifference = operator.applyAsDouble(smallFunEvaluation, largeFunEvaluation);
final double actualEvaluation = smallLargeDifference.evaluate(timeToEvaluateAt);
assertEquals(expectedDifference, actualEvaluation, delta);
}
}
}
@Test
public void testDifferenceSomeOverlapLargeMinusMiddle(){
DoubleBinaryOperator operator = (a, b) -> a - b;
final RightContinuousStepFunction combinedFunction = RightContinuousStepFunction.biOperation(
largeNumbers,
middleNumbers,
operator);
assertEquals(6, combinedFunction.getX().length);
assertEquals(6, combinedFunction.getY().length);
final double[] offsetTimes = {-0.1, 0.0, 0.1};
for(int time = 1; time <= 9; time++){
for(double offsetTime : offsetTimes){
final double timeToEvaluateAt = (double) time + offsetTime;
final double middleFunEvaluation = middleNumbers.evaluate(timeToEvaluateAt);
final double largeFunEvaluation = largeNumbers.evaluate(timeToEvaluateAt);
final double expectedDifference = operator.applyAsDouble(largeFunEvaluation, middleFunEvaluation);
final double actualEvaluation = combinedFunction.evaluate(timeToEvaluateAt);
assertEquals(expectedDifference, actualEvaluation, delta);
}
}
}
@Test
public void testDifferenceCompleteOverlap(){
DoubleBinaryOperator operator = (a, b) -> a - b;
final RightContinuousStepFunction combinedFunction = RightContinuousStepFunction.biOperation(
middleNumbers,
middleNumbers,
operator);
assertEquals(4, combinedFunction.getX().length);
assertEquals(4, combinedFunction.getY().length);
final double[] offsetTimes = {-0.1, 0.0, 0.1};
for(int time = 1; time <= 9; time++){
for(double offsetTime : offsetTimes){
final double timeToEvaluateAt = (double) time + offsetTime;
final double actualEvaluation = combinedFunction.evaluate(timeToEvaluateAt);
assertEquals(0.0, actualEvaluation, delta);
}
}
}
@Test
public void testPowerFunction(){
final DoubleUnaryOperator operator = d -> d*d;
final RightContinuousStepFunction squaredFunction = smallNumbers.unaryOperation(operator);
assertEquals(4, squaredFunction.getX().length);
assertEquals(4, squaredFunction.getY().length);
final double[] offsetTimes = {-0.1, 0.0, 0.1};
for(int time = 1; time <= 9; time++){
for(double offsetTime : offsetTimes){
final double timeToEvaluateAt = (double) time + offsetTime;
final double expectedEvaluation = operator.applyAsDouble(smallNumbers.evaluate(timeToEvaluateAt));
final double actualEvaluation = squaredFunction.evaluate(timeToEvaluateAt);
assertEquals(expectedEvaluation, actualEvaluation, delta);
}
}
}
}