From 9c2c617bfff1104112bbc519d7dd975e99c15640 Mon Sep 17 00:00:00 2001 From: Martin Veillette Date: Tue, 14 Mar 2023 13:48:20 -0400 Subject: [PATCH] Replace base curve by originalCurve (see https://github.com/phetsims/calculus-grapher/issues/275) Signed-off-by: Martin Veillette --- js/common/model/DerivativeCurve.ts | 44 +++++++++----------- js/common/model/IntegralCurve.ts | 44 +++++++++----------- js/common/model/SecondDerivativeCurve.ts | 52 ++++++++++++------------ 3 files changed, 65 insertions(+), 75 deletions(-) diff --git a/js/common/model/DerivativeCurve.ts b/js/common/model/DerivativeCurve.ts index e5f4e73d..e7199ca5 100644 --- a/js/common/model/DerivativeCurve.ts +++ b/js/common/model/DerivativeCurve.ts @@ -1,15 +1,14 @@ // Copyright 2020-2023, University of Colorado Boulder /** - * DerivativeCurve is a Curve subclass for a curve that represents the derivative of a 'base' curve. It is used - * as both the first derivative and second derivative of the original curve. + * DerivativeCurve is a Curve subclass for a curve that represents the derivative of a Curve. It is used + * to evaluate the first derivative of the original curve. * - * DerivativeCurves' main responsibility is to observe when the 'base' Curve changes and differentiates it and update + * DerivativeCurves' main responsibility is to observe when the original Curve changes and differentiates it and update * the Points of the derivative. Derivatives are computed by considering the slope of the secant lines from both sides * of every point. For a general background on differentiation, see * https://en.wikipedia.org/wiki/Derivative#Rigorous_definition. * - * * Like Curve, DerivativeCurve is created at the start and persists for the lifetime of the simulation. Links * are left as-is and DerivativeCurves are never disposed. * @@ -23,58 +22,55 @@ import Curve from './Curve.js'; export default class DerivativeCurve extends Curve { - // Reference to the 'base' Curve that was passed-in. - private readonly baseCurve: Curve; + // Reference to the originalCurve that was passed-in. + private readonly originalCurve: Curve; /** - * @param baseCurve - the curve to differentiate to get the values of this DerivativeCurve + * @param originalCurve - the curve to differentiate to get the values for this DerivativeCurve * @param tandem */ - public constructor( baseCurve: Curve, tandem: Tandem ) { + public constructor( originalCurve: Curve, tandem: Tandem ) { super( { // CurveOptions - xRange: baseCurve.xRange, - numberOfPoints: baseCurve.numberOfPoints, + xRange: originalCurve.xRange, + numberOfPoints: originalCurve.numberOfPoints, tandem: tandem } ); - this.baseCurve = baseCurve; + this.originalCurve = originalCurve; - // Observes when the 'base' Curve changes and update this curve to represent the derivative of the 'base' Curve. + // Observes when the originalCurve changes and update this curve to represent the derivative. // Listener is never removed since DerivativeCurves are never disposed. - baseCurve.curveChangedEmitter.addListener( this.updateDerivative.bind( this ) ); + originalCurve.curveChangedEmitter.addListener( this.updateDerivative.bind( this ) ); - // Makes the initial call to updateDerivative() to match the 'base' Curve upon initialization. + // Makes the initial call to updateDerivative() to match the originalCurve upon initialization. this.updateDerivative(); } /** - * Updates the y-values of the DerivativeCurve to represent the derivative of the 'base' Curve. + * Updates the y-values of the DerivativeCurve to represent the derivative of the originalCurve. * * The derivative is approximated as the slope of the secant line between each adjacent Point. * Our version considers both the slope of the secant lines from the left and right side of every point. See * https://en.wikipedia.org/wiki/Numerical_differentiation * - * Since the 'Calculus Grapher' sim has second derivatives, the 'base' curve could have cusps and/or non-finite - * points. The algorithm for computing derivatives works by iterating through each Point of the 'base' Curve. - * * TODO https://github.com/phetsims/calculus-grapher/issues/110 add documentation */ private updateDerivative(): void { - const basePoints = this.baseCurve.points; + const originalPoints = this.originalCurve.points; - const length = basePoints.length; + const length = originalPoints.length; let leftSlope: number | null; let rightSlope: number | null; for ( let index = 0; index < length; index++ ) { - const previousPoint = index > 0 ? basePoints[ index - 1 ] : null; - const point = basePoints[ index ]; - const nextPoint = index < length - 1 ? basePoints[ index + 1 ] : null; + const previousPoint = index > 0 ? originalPoints[ index - 1 ] : null; + const point = originalPoints[ index ]; + const nextPoint = index < length - 1 ? originalPoints[ index + 1 ] : null; if ( previousPoint === null || point.isCusp && previousPoint.isCusp || ( point.isDiscontinuous && previousPoint.isDiscontinuous ) ) { @@ -92,7 +88,7 @@ export default class DerivativeCurve extends Curve { } if ( typeof leftSlope === 'number' && typeof rightSlope === 'number' ) { - // If both the left and right adjacent Points of the Point of the 'base' curve exist, the derivative is + // If both the left and right adjacent Points of the Point of the originalCurve exist, the derivative is // the average of the slopes if they are approximately equal. Otherwise, the derivative doesn't exist. this.points[ index ].y = ( leftSlope + rightSlope ) / 2; } diff --git a/js/common/model/IntegralCurve.ts b/js/common/model/IntegralCurve.ts index 4df30212..ac7d1eac 100644 --- a/js/common/model/IntegralCurve.ts +++ b/js/common/model/IntegralCurve.ts @@ -1,13 +1,11 @@ // Copyright 2020-2023, University of Colorado Boulder /** - * IntegralCurve is a Curve subclass for the curve that represents the integral of the TransformedCurve. - * The TransformedCurve is referenced as the 'base' Curve of the IntegralCurve. - * - * IntegralCurve's main responsibility is to observe when the 'base' Curve changes and integrate it and update the + * IntegralCurve is a Curve subclass for the curve that represents the integral of originalCurve. + * IntegralCurve's main responsibility is to observe when the Curve changes and integrate it and update the * Points of the Integral. For a general background on integration, see https://en.wikipedia.org/wiki/Integral. Our * version uses a trapezoidal Riemann sum to approximate integrals. See https://en.wikipedia.org/wiki/Trapezoidal_rule - * for background. Since the 'base' Curve exists at all Points, the Integral is also finite at all points. + * for background. Since the originalCurve exists at all Points, the Integral is also finite at all points. * * Like Curve, IntegralCurve is created at the start and persists for the lifetime of the simulation. Links are left * as-is and IntegralCurves are never disposed. @@ -15,57 +13,53 @@ * @author Brandon Li * @author Martin Veillette */ - import Tandem from '../../../../tandem/js/Tandem.js'; import calculusGrapher from '../../calculusGrapher.js'; import Curve from './Curve.js'; export default class IntegralCurve extends Curve { - // Reference to the 'base' Curve that was passed-in. - private readonly baseCurve: Curve; + // Reference to the originalCurve that was passed-in. + private readonly originalCurve: Curve; /** - * @param baseCurve - the curve to integrate to get the values of this IntegralCurve. + * @param originalCurve - the curve to integrate to get the values of this IntegralCurve. * @param tandem */ - public constructor( baseCurve: Curve, tandem: Tandem ) { + public constructor( originalCurve: Curve, tandem: Tandem ) { super( { // CurveOptions - xRange: baseCurve.xRange, - numberOfPoints: baseCurve.numberOfPoints, + xRange: originalCurve.xRange, + numberOfPoints: originalCurve.numberOfPoints, tandem: tandem } ); - this.baseCurve = baseCurve; + this.originalCurve = originalCurve; - // Observes when the 'base' Curve changes and update this curve to represent the integral of the 'base' Curve. + // Observes when the originalCurve changes and update this curve to represent the integral of the originalCurve. // Listener is never removed since IntegralCurves are never disposed. - baseCurve.curveChangedEmitter.addListener( this.updateIntegral.bind( this ) ); + originalCurve.curveChangedEmitter.addListener( this.updateIntegral.bind( this ) ); - // Makes the initial call to update the integral to match the 'base' Curve upon initialization. + // Makes the initial call to update the integral to match the originalCurve upon initialization. this.updateIntegral(); } /** - * Updates the y-values of the IntegralCurve to represent the integral of the 'base' Curve. + * Updates the y-values of the IntegralCurve to represent the integral of the originalCurve. * * The integral is approximated by performing a Riemann Sum. A left Riemann Sum is used - * to determine the area of a series of rectangles to approximate the area under a curve. The left Riemann Sum + * to determine the area from a series of rectangles to approximate the area under a curve. The left Riemann Sum * uses the left side of the function for the height of the rectangle summing up all * trapezoidal areas. See https://en.wikipedia.org/wiki/Riemann_sum for more details. - * - * The IntegralCurve exists at all points since TransformedCurve is finite at all points, so we don't need to consider - * non-differentiable or non-finite points of the 'base' curve. */ private updateIntegral(): void { - // Loop through each pair of adjacent Points of the base Curve. - for ( let index = 1; index < this.baseCurve.points.length; index++ ) { - const point = this.baseCurve.points[ index ]; - const previousPoint = this.baseCurve.points[ index - 1 ]; + // Loop through each pair of adjacent points of the original curve. + for ( let index = 1; index < this.originalCurve.points.length; index++ ) { + const point = this.originalCurve.points[ index ]; + const previousPoint = this.originalCurve.points[ index - 1 ]; assert && assert( point.isFinite && previousPoint.isFinite ); diff --git a/js/common/model/SecondDerivativeCurve.ts b/js/common/model/SecondDerivativeCurve.ts index ec926bf4..e37103d2 100644 --- a/js/common/model/SecondDerivativeCurve.ts +++ b/js/common/model/SecondDerivativeCurve.ts @@ -1,9 +1,10 @@ // Copyright 2023, University of Colorado Boulder /** - * SecondDerivative is a Curve subclass for a curve that represents the second derivative of a 'base' curve. + * SecondDerivative is a Curve subclass for a curve that represents the second derivative of a Curve. + * It is used to evaluate the second derivative of the originalCurve. * - * SecondDerivatives' main responsibility is to observe when the 'base' Curve changes and differentiates it and update + * SecondDerivatives' main responsibility is to observe when the originalCurve changes and differentiates it and update * the Points of the second derivative. * * Like Curve, SecondDerivative is created at the start and persists for the lifetime of the simulation. Links @@ -18,70 +19,69 @@ import Curve from './Curve.js'; export default class SecondDerivativeCurve extends Curve { - // Reference to the 'base' Curve that was passed-in. - private readonly baseCurve: Curve; + // Reference to the originalCurve that was passed-in. + private readonly originalCurve: Curve; /** - * @param baseCurve - the curve to differentiate to get the values of this SecondDerivative + * @param originalCurve - the curve to differentiate to get the values of this SecondDerivative * @param tandem */ - public constructor( baseCurve: Curve, tandem: Tandem ) { + public constructor( originalCurve: Curve, tandem: Tandem ) { super( { // CurveOptions - xRange: baseCurve.xRange, - numberOfPoints: baseCurve.numberOfPoints, + xRange: originalCurve.xRange, + numberOfPoints: originalCurve.numberOfPoints, tandem: tandem } ); - this.baseCurve = baseCurve; + this.originalCurve = originalCurve; - // Observes when the 'base' Curve changes and update this curve to represent the second derivative of the 'base' Curve. + // Observes when the originalCurve changes and update this curve to represent the second derivative of the originalCurve. // Listener is never removed since SecondDerivative is never disposed. - baseCurve.curveChangedEmitter.addListener( this.updateSecondDerivative.bind( this ) ); + originalCurve.curveChangedEmitter.addListener( this.updateSecondDerivative.bind( this ) ); - // Makes the initial call to updateSecondDerivative() to match the 'base' Curve upon initialization. + // Makes the initial call to updateSecondDerivative() to match the originalCurve upon initialization. this.updateSecondDerivative(); } /** - * Updates the y-values of the SecondDerivative to represent the derivative of the 'base' Curve. + * Updates the y-values of the SecondDerivative to represent the derivative of the originalCurve. * * To update the second derivative, we (1) assume that the points are smooth, * and evaluate the second derivative using the standard finite difference algorithm. * For points that are not smooth, we correct for the wrong assumption, by assigning * their second derivative to a smooth point directly next to it. - * */ private updateSecondDerivative(): void { // Convenience variables - const basePoints = this.baseCurve.points; - const length = basePoints.length; + const originalPoints = this.originalCurve.points; + const length = originalPoints.length; for ( let index = 0; index < length; index++ ) { - // Is the base point smooth? - const isBasePointSmooth = basePoints[ index ].pointType === 'smooth'; + // Is the original point smooth? + const isOriginalPointSmooth = originalPoints[ index ].pointType === 'smooth'; - // The point type is the same as the base point, unless the base point is not smooth, in which case it must be discontinuous - this.points[ index ].pointType = isBasePointSmooth ? 'smooth' : 'discontinuous'; + // The point type is the same as the original point, unless the original point is not smooth, in which case it must be discontinuous + this.points[ index ].pointType = isOriginalPointSmooth ? 'smooth' : 'discontinuous'; // We exclude the first and last point. They will be dealt with later if ( index !== 0 && index !== length - 1 ) { - const previousPoint = basePoints[ index - 1 ]; - const point = basePoints[ index ]; - const nextPoint = basePoints[ index + 1 ]; + const previousPoint = originalPoints[ index - 1 ]; + const point = originalPoints[ index ]; + const nextPoint = originalPoints[ index + 1 ]; - // Determine the second derivative using the naive assumption that all points are smooth. We will handle exceptions later + // Determine the second derivative using the naive assumption that all original points are smooth. We will handle exceptions later this.points[ index ].y = ( point.getSlope( nextPoint ) - point.getSlope( previousPoint ) ) / ( 2 * this.deltaX ); } } // Handle the y value of the first and last point - this.points[ 0 ].y = ( basePoints[ 1 ].pointType === 'smooth' ) ? this.points[ 1 ].y : 0; - this.points[ length - 1 ].y = ( basePoints[ length - 2 ].pointType === 'smooth' ) ? this.points[ length - 2 ].y : 0; + this.points[ 0 ].y = ( originalPoints[ 1 ].pointType === 'smooth' ) ? this.points[ 1 ].y : 0; + this.points[ length - 1 ].y = ( originalPoints[ length - 2 ].pointType === 'smooth' ) ? this.points[ length - 2 ].y : 0; // Reiterate over points but this time taking into account the point type