|
JMeter example source code file (Spline3.java)
The JMeter Spline3.java source code
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jmeter.visualizers;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
/*
* TODO : - implement ImageProducer interface - suggestions ;-)
*/
/**
* This class implements the representation of an interpolated Spline curve.
* <P>
* The curve described by such an object interpolates an arbitrary number of
* fixed points called <I>nodes. The distance between two nodes should
* currently be constant. This is about to change in a later version but it can
* last a while as it's not really needed. Nevertheless, if you need the
* feature, just <a href="mailto:norguet@bigfoot.com?subject=Spline3eq">write me
* a note</a> and I'll write it asap.
* <P>
* The interpolated Spline curve can't be described by an polynomial analytic
* equation, the degree of which would be as high as the number of nodes, which
* would cause extreme oscillations of the curve on the edges.
* <P>
* The solution is to split the curve accross a lot of little <I>intervals :
* an interval starts at one node and ends at the next one. Then, the
* interpolation is done on each interval, according to the following conditions :
* <OL>
* <LI>the interpolated curve is degree 3 : it's a cubic curve ;
* <LI>the interpolated curve contains the two points delimiting the interval.
* This condition obviously implies the curve is continuous ;
* <LI>the interpolated curve has a smooth slope : the curvature has to be the
* same on the left and the right sides of each node ;
* <LI>the curvature of the global curve is 0 at both edges.
* </OL>
* Every part of the global curve is represented by a cubic (degree-3)
* polynomial, the coefficients of which have to be computed in order to meet
* the above conditions.
* <P>
* This leads to a n-unknow n-equation system to resolve. One can resolve an
* equation system by several manners ; this class uses the Jacobi iterative
* method, particularly well adapted to this situation, as the diagonal of the
* system matrix is strong compared to the other elements. This implies the
* algorithm always converges ! This is not the case of the Gauss-Seidel
* algorithm, which is quite faster (it uses intermediate results of each
* iteration to speed up the convergence) but it doesn't converge in all the
* cases or it converges to a wrong value. This is not acceptable and that's why
* the Jacobi method is safer. Anyway, the gain of speed is about a factor of 3
* but, for a 100x100 system, it means 10 ms instead of 30 ms, which is a pretty
* good reason not to explore the question any further :)
* <P>
* Here is a little piece of code showing how to use this class :
*
* <PRE> // ... float[] nodes = {3F, 2F, 4F, 1F, 2.5F, 5F, 3F}; Spline3 curve =
* new Spline3(nodes); // ... public void paint(Graphics g) { int[] plot =
* curve.getPlots(); for (int i = 1; i < n; i++) { g.drawLine(i - 1, plot[i -
* 1], i, plot[i]); } } // ...
*
* </PRE>
*
*/
public class Spline3 {
private static final Logger log = LoggingManager.getLoggerForClass();
protected float[][] _coefficients;
protected float[][] _A;
protected float[] _B;
protected float[] _r;
protected float[] _rS;
protected int _m; // number of nodes
protected int _n; // number of non extreme nodes (_m-2)
final static protected float DEFAULT_PRECISION = (float) 1E-1;
final static protected int DEFAULT_MAX_ITERATIONS = 100;
protected float _minPrecision = DEFAULT_PRECISION;
protected int _maxIterations = DEFAULT_MAX_ITERATIONS;
/**
* Creates a new Spline curve by calculating the coefficients of each part
* of the curve, i.e. by resolving the equation system implied by the
* interpolation condition on every interval.
*
* @param r
* an array of float containing the vertical coordinates of the
* nodes to interpolate ; the vertical coordinates start at 0 and
* are equidistant with a step of 1.
*/
public Spline3(float[] r) {
int n = r.length;
// the number of nodes is defined by the length of r
this._m = n;
// grab the nodes
this._r = new float[n];
for (int i = 0; i < n; i++) {
_r[i] = r[i];
}
// the number of non extreme nodes is the number of intervals
// minus 1, i.e. the length of r minus 2
this._n = n - 2;
// computes interpolation coefficients
try {
long startTime = System.currentTimeMillis();
this.interpolation();
if (log.isDebugEnabled()) {
long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime;
if (log.isDebugEnabled()) {
log.debug("New Spline curve interpolated in ");
log.debug(elapsedTime + " ms");
}
}
} catch (Exception e) {
log.error("Error when interpolating : ", e);
}
}
/**
* Computes the coefficients of the Spline interpolated curve, on each
* interval. The matrix system to resolve is <CODE>AX=B
*/
protected void interpolation() {
// creation of the interpolation structure
_rS = new float[_m];
_B = new float[_n];
_A = new float[_n][_n];
_coefficients = new float[_n + 1][4];
// local variables
int i = 0, j = 0;
// initialize system structures (just to be safe)
for (i = 0; i < _n; i++) {
_B[i] = 0;
for (j = 0; j < _n; j++) {
_A[i][j] = 0;
}
for (j = 0; j < 4; j++) {
_coefficients[i][j] = 0;
}
}
for (i = 0; i < _n; i++) {
_rS[i] = 0;
}
// initialize the diagonal of the system matrix (A) to 4
for (i = 0; i < _n; i++) {
_A[i][i] = 4;
}
// initialize the two minor diagonals of A to 1
for (i = 1; i < _n; i++) {
_A[i][i - 1] = 1;
_A[i - 1][i] = 1;
}
// initialize B
for (i = 0; i < _n; i++) {
_B[i] = 6 * (_r[i + 2] - 2 * _r[i + 1] + _r[i]);
}
// Jacobi system resolving
this.jacobi(); // results are stored in _rS
// computes the coefficients (di, ci, bi, ai) from the results
for (i = 0; i < _n + 1; i++) {
// di (degree 0)
_coefficients[i][0] = _r[i];
// ci (degree 1)
_coefficients[i][1] = _r[i + 1] - _r[i] - (_rS[i + 1] + 2 * _rS[i]) / 6;
// bi (degree 2)
_coefficients[i][2] = _rS[i] / 2;
// ai (degree 3)
_coefficients[i][3] = (_rS[i + 1] - _rS[i]) / 6;
}
}
/**
* Resolves the equation system by a Jacobi algorithm. The use of the slower
* Jacobi algorithm instead of Gauss-Seidel is choosen here because Jacobi
* is assured of to be convergent for this particular equation system, as
* the system matrix has a strong diagonal.
*/
protected void jacobi() {
// local variables
int i = 0, j = 0, iterations = 0;
// intermediate arrays
float[] newX = new float[_n];
float[] oldX = new float[_n];
// Jacobi convergence test
if (!converge()) {
if (log.isDebugEnabled()) {
log.debug("Warning : equation system resolving is unstable");
}
}
// init newX and oldX arrays to 0
for (i = 0; i < _n; i++) {
newX[i] = 0;
oldX[i] = 0;
}
// main iteration
while ((this.precision(oldX, newX) > this._minPrecision) && (iterations < this._maxIterations)) {
for (i = 0; i < _n; i++) {
oldX[i] = newX[i];
}
for (i = 0; i < _n; i++) {
newX[i] = _B[i];
for (j = 0; j < i; j++) {
newX[i] = newX[i] - (_A[i][j] * oldX[j]);
}
for (j = i + 1; j < _n; j++) {
newX[i] = newX[i] - (_A[i][j] * oldX[j]);
}
newX[i] = newX[i] / _A[i][i];
}
iterations++;
}
if (this.precision(oldX, newX) < this._minPrecision) {
if (log.isDebugEnabled()) {
log.debug("Minimal precision (");
log.debug(this._minPrecision + ") reached after ");
log.debug(iterations + " iterations");
}
} else if (iterations > this._maxIterations) {
if (log.isDebugEnabled()) {
log.debug("Maximal number of iterations (");
log.debug(this._maxIterations + ") reached");
log.debug("Warning : precision is only ");
log.debug("" + this.precision(oldX, newX));
log.debug(", divergence is possible");
}
}
for (i = 0; i < _n; i++) {
_rS[i + 1] = newX[i];
}
}
/**
* Test if the Jacobi resolution of the equation system converges. It's OK
* if A has a strong diagonal.
*/
protected boolean converge() {
boolean converge = true;
int i = 0, j = 0;
float lineSum = 0F;
for (i = 0; i < _n; i++) {
if (converge) {
lineSum = 0;
for (j = 0; j < _n; j++) {
lineSum = lineSum + Math.abs(_A[i][j]);
}
lineSum = lineSum - Math.abs(_A[i][i]);
if (lineSum > Math.abs(_A[i][i])) {
converge = false;
}
}
}
return converge;
}
/**
* Computes the current precision reached.
*/
protected float precision(float[] oldX, float[] newX) {
float N = 0F, D = 0F, erreur = 0F;
int i = 0;
for (i = 0; i < _n; i++) {
N = N + Math.abs(newX[i] - oldX[i]);
D = D + Math.abs(newX[i]);
}
if (D != 0F) {
erreur = N / D;
} else {
erreur = Float.MAX_VALUE;
}
return erreur;
}
/**
* Computes a (vertical) Y-axis value of the global curve.
*
* @param t
* abscissa
* @return computed ordinate
*/
public float value(float t) {
int i = 0, splineNumber = 0;
float abscissa = 0F, result = 0F;
// verify t belongs to the curve (range [0, _m-1])
if ((t < 0) || (t > (_m - 1))) {
if (log.isDebugEnabled()) {
log.debug("Warning : abscissa " + t + " out of bounds [0, " + (_m - 1) + "]");
}
// silent error, consider the curve is constant outside its range
if (t < 0) {
t = 0;
} else {
t = _m - 1;
}
}
// seek the good interval for t and get the piece of curve on it
splineNumber = (int) Math.floor(t);
if (t == (_m - 1)) {
// the upper limit of the curve range belongs by definition
// to the last interval
splineNumber--;
}
// computes the value of the curve at the pecified abscissa
// and relative to the beginning of the right piece of Spline curve
abscissa = t - splineNumber;
// the polynomial calculation is done by the (fast) Euler method
for (i = 0; i < 4; i++) {
result = result * abscissa;
result = result + _coefficients[splineNumber][3 - i];
}
return result;
}
/**
* Manual check of the curve at the interpolated points.
*/
public void debugCheck() {
int i = 0;
for (i = 0; i < _m; i++) {
log.info("Point " + i + " : ");
log.info(_r[i] + " =? " + value(i));
}
}
/**
* Computes drawable plots from the curve for a given draw space. The values
* returned are drawable vertically and from the <B>bottom of a Panel.
*
* @param width
* width within the plots have to be computed
* @param height
* height within the plots are expected to be drawed
* @return drawable plots within the limits defined, in an array of int (as
* many int as the value of the <CODE>width parameter)
*/
public int[] getPlots(int width, int height) {
int[] plot = new int[width];
// computes auto-scaling and absolute plots
float[] y = new float[width];
float max = java.lang.Integer.MIN_VALUE;
float min = java.lang.Integer.MAX_VALUE;
for (int i = 0; i < width; i++) {
y[i] = value(((float) i) * (_m - 1) / width);
if (y[i] < min) {
min = y[i];
}
if (y[i] > max) {
max = y[i];
}
}
if (min < 0) {
min = 0; // shouldn't draw negative values
}
// computes relative auto-scaled plots to fit in the specified area
for (int i = 0; i < width; i++) {
plot[i] = Math.round(((y[i] - min) * (height - 1)) / (max - min));
}
return plot;
}
public void setPrecision(float precision) {
this._minPrecision = precision;
}
public float getPrecision() {
return this._minPrecision;
}
public void setToDefaultPrecision() {
this._minPrecision = DEFAULT_PRECISION;
}
public float getDefaultPrecision() {
return DEFAULT_PRECISION;
}
public void setMaxIterations(int iterations) {
this._maxIterations = iterations;
}
public int getMaxIterations() {
return this._maxIterations;
}
public void setToDefaultMaxIterations() {
this._maxIterations = DEFAULT_MAX_ITERATIONS;
}
public int getDefaultMaxIterations() {
return DEFAULT_MAX_ITERATIONS;
}
}
Other JMeter examples (source code examples)Here is a short list of links related to this JMeter Spline3.java source code file: |
| ... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.