Java 实现自适应函数绘图

Java 实现自适应函数绘图,java,algorithm,recursion,numerical-methods,Java,Algorithm,Recursion,Numerical Methods,我试图使用这两个示例中的伪代码实现自适应函数绘图算法(两个示例实际上是相同的) 我遇到的问题(可能是由于我不正确的实现)有: (1)正在创建重复的坐标。我知道这是因为分裂,每个分裂都保留两端,所以一个间隔的结束是另一个间隔的开始-相同的x值计算两次。但是有没有一种方法可以设置算法来避免复杂的坐标。我可以避免添加起始坐标或结束坐标作为一个脏补丁(请参阅下面代码中的注释),但是在整个时间间隔内,我将丢失一个 (2)绘图的某些部分缺少基本上是对称函数的坐标,这很奇怪。算法应该以同样的方式适用于双方

我试图使用这两个示例中的伪代码实现自适应函数绘图算法(两个示例实际上是相同的)

我遇到的问题(可能是由于我不正确的实现)有:

(1)正在创建重复的坐标。我知道这是因为分裂,每个分裂都保留两端,所以一个间隔的结束是另一个间隔的开始-相同的x值计算两次。但是有没有一种方法可以设置算法来避免复杂的坐标。我可以避免添加起始坐标或结束坐标作为一个脏补丁(请参阅下面代码中的注释),但是在整个时间间隔内,我将丢失一个

(2)绘图的某些部分缺少基本上是对称函数的坐标,这很奇怪。算法应该以同样的方式适用于双方,但事实并非如此。当深度>=6时,就会发生这种情况,它会对函数的右侧进行额外拆分,而对原点周围的左侧不进行任何拆分。远离原点的其余坐标似乎匹配。右侧的裂口似乎比左侧的总裂口多

问题(2)

我的算法实现

static List<Double[]> linePoints;

public static void main(String[] args) {
    linePoints =  new ArrayList<>();

    double startX = -50;
    double endX = 50;

    sampling(startX, endX, depth, tolerance);

    /* Print all points to be plotted - x,y coordinates */
    for (Double[] point : linePoints) {
        System.out.println(point[0]+","+point[1]);
    }
}

/* math function */
public static double f(double x){
    return x*Math.sin(x);
}


static int depth = 6; /* 8 */
static double tolerance = 0.005; /* just a guess */

/* Adaptive sampling algorithm */
/* mostly followed along 2st website and used 1st website variable names  */
public static void sampling(double xa, double xc, int depth, double tolerance){
    /* step (1) of 2nd website - determine mid-intervals */
    double xb = (xa+xc)/2;   /* (xc-xa)/2; tried these from 1st website - didn't work out */
    double xab = (xa+xb)/2;  /* (xb-xa)/2; */
    double xbc = (xb+xc)/2;  /* (xc-xb)/2; */

    /* evaluate the above points using math function - store in array */
    double[] points = new double[5];
    points[0] = f(xa); points[1] = f(xab); points[2] = f(xb); points[3] = f(xbc); points[4] = f(xc);

    /* step (2) of 2nd website */
    if (depth <= 0){
        linePoints.add(new Double[]{xa, points[0]});  /* either I comment out this line for dirty fix */
        linePoints.add(new Double[]{xab, points[1]});
        linePoints.add(new Double[]{xb, points[2]});
        linePoints.add(new Double[]{xbc, points[3]});
        linePoints.add(new Double[]{xc, points[4]});  /* or comment out this line */
    } else {
        /* step (3) of 2nd website */
        int counter = 0;

        for (int i = 1; i < points.length-1; i++){
            /* Check if prev, current, next values are infinite or NaN */
            if (    (Double.isInfinite(points[i-1]) || Double.isNaN(points[i-1])) ||
                    (Double.isInfinite(points[i]) || Double.isNaN(points[i])) ||
                    (Double.isInfinite(points[i+1]) || Double.isNaN(points[i+1]))){
                counter++;
                continue;
            }

            /* Determine the fluctuations - if current is < or > both it's left/right neighbours */
            boolean middleLarger = (points[i] > points[i-1]) && (points[i] > points[i+1]);
            boolean middleSmaller = (points[i] < points[i-1]) && (points[i] < points[i+1]);

            if (middleLarger || middleSmaller){
                counter++;
            }
        }

        if (counter <= 2){  /* at most 2 */
            /* Newton-Cotes quadratures - check if smooth enough */
            double f1 = (3d/8d)*points[0]+(19d/24d)*points[1]-(5d/24d)*points[2]+(1d/24d)*points[3];    /* add 'd' to end of number, otherwise get 0 always */
            double f2 = (5d/12d)*points[2]+(2d/3d)*points[3]-(1d/12d)*points[4];

         if (Math.abs(f1-f2) < tolerance * f2){
                linePoints.add(new Double[]{xa, points[0]});
                linePoints.add(new Double[]{xab, points[1]});
                linePoints.add(new Double[]{xb, points[2]});
                linePoints.add(new Double[]{xbc, points[3]});
                linePoints.add(new Double[]{xc, points[4]});
            } else {
                /* not smooth enough - needs more refinement */
                depth--;
                tolerance *= 2;
                sampling(xa, xb, depth, tolerance);
                sampling(xb, xc, depth, tolerance);
            }

        } else {
            /* else (count > 2), that means further splittings are needed to produce more accurate samples */
            depth--;
            tolerance *= 2;
            sampling(xa, xb, depth, tolerance);
            sampling(xb, xc, depth, tolerance);
        }
    }
}
静态列表线点;
公共静态void main(字符串[]args){
linePoints=newarraylist();
双startX=-50;
双端x=50;
取样(起点、终点、深度、公差);
/*打印所有要打印的点-x、y坐标*/
对于(双[]点:线点){
System.out.println(点[0]+“,”+点[1]);
}
}
/*数学函数*/
公共静态双f(双x){
返回x*Math.sin(x);
}
静态整数深度=6;/*8 */
静态双公差=0.005;/*只是猜测*/
/*自适应采样算法*/
/*主要跟随2st网站并使用第一个网站变量名*/
公共静态空隙取样(双xa、双xc、int深度、双公差){
/*第二个网站的步骤(1)-确定中间间隔*/
double xb=(xa+xc)/2;/*(xc-xa)/2;在第一个网站上尝试了这些-没有成功*/
双xab=(xa+xb)/2;/*(xb-xa)/2*/
双xbc=(xb+xc)/2;/*(xc-xb)/2*/
/*使用数学函数-store in array计算上述各点*/
双[]点=新双[5];
点[0]=f(xa);点[1]=f(xab);点[2]=f(xb);点[3]=f(xbc);点[4]=f(xc);
/*第二个网站的步骤(2)*/
如果(深度)都是左/右邻域*/
布尔值=(点[i]>点[i-1])&(点[i]>点[i+1]);
布尔中值=(点[i]<点[i-1])&(点[i]<点[i+1]);
if(中大| |中小){
计数器++;
}
}
如果(计数器2),则意味着需要进一步拆分以生成更精确的样本*/
深度--;
公差*=2;
取样(xa、xb、深度、公差);
取样(xb、xc、深度、公差);
}
}
}
修复-修改我的代码

看看Gene的例子,将耐受性乘以0.5而不是2似乎可以解决问题(2)


Genes example是该算法更好、更干净的实现,并处理重复的坐标

我认为您已经忠实地实现了,但该算法被破坏了。不对称求积很容易得到不对称的结果。我得到了同样的奇怪

但是,您可以通过在排序集中维护重复点并使用在递归分析器运行时端点已经插入的不变量来消除重复点

下面是一个更全面地使用现代Java功能的简化:

import static java.lang.Double.compare;
import static java.lang.Double.isFinite;
import static java.lang.Math.PI;

import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.DoubleUnaryOperator;
import static java.util.stream.Collectors.toList;
import java.util.stream.DoubleStream;

public class AdaptivePlot {
  private final DoubleUnaryOperator f;
  private final double a;
  private final double c;
  private final SortedSet<Point> plot = new TreeSet<>((s, t) -> compare(s.x, t.x));

  public AdaptivePlot(DoubleUnaryOperator f, double a, double c) {
    this.f = f;
    this.a = a;
    this.c = c;
  }

  public static class Point {
    final double x, y;
    public Point(double x, double y) {
      this.x = x;
      this.y = y;
    }
  }

  public AdaptivePlot computePlot(int depth, double eps) {
    plot.clear();
    Point pa = pointAt(a);
    Point pc = pointAt(c);
    plot.add(pa);
    plot.add(pc);
    computePlot(pa, pc, depth, eps);
    return this;
  }

  public List<Point> getPlot() {
    return plot.stream().collect(toList());
  }

  private Point pointAt(double x) {
    return new Point(x, f.applyAsDouble(x));
  }

  private void computePlot(Point pa, Point pc, int depth, double eps) {
    Point pb = pointAt(0.5 * (pa.x + pc.x));
    Point pa1 = pointAt(0.5 * (pa.x + pb.x));
    Point pb1 = pointAt(0.5 * (pb.x + pc.x));
    plot.add(pb);
    if (depth > 0 && 
        (oscillates(pa.y, pa1.y, pb.y, pb1.y, pc.y) 
            || unsmooth(pa.y, pa1.y, pb.y, pb1.y, pc.y, eps))) {
      computePlot(pa, pb, depth - 1, 2 * eps);
      computePlot(pb, pc, depth - 1, 2 * eps);
    }
    plot.add(pa1);
    plot.add(pb1);
  }

  private static boolean oscillates(
      double ya, double ya1, double yb, double yb1, double yc) {
    return isOscillation(ya, ya1, yb) 
        && isOscillation(ya1, yb, yb1) 
        && isOscillation(yb, yb1, yc);
  }

  private static boolean isOscillation(double ya, double yb, double yc) {
    return !isFinite(ya) || !isFinite(yb) || !isFinite(yc) 
        || (yb > ya && yb > yc) || (yb < ya && yb < yc);
  }

  private static boolean unsmooth(
      double ya, double ya1, double yb, double yb1,double yc, double eps) {
    double y0 = DoubleStream.of(ya, ya1, yb, yb1, yc).min().getAsDouble();
    double [] yg = DoubleStream.of(ya, ya1, yb, yb1, yc).map(y -> y - y0).toArray();
    double q4 = quadrature(yg[0], yg[1], yg[2], yg[3]);
    double q3 = quadrature(yg[2], yg[3], yg[4]);
    return Math.abs(q4 - q3) > eps * q3;
  }

  private static double quadrature(double y0, double y1, double y2, double y3) {
    return 3d/8d * y0 + 19d/24d * y1 - 5d/24d * y2 + 1d/24d * y3;
  }

  private static double quadrature(double y0, double y1, double y2) {
    return 5d/12d * y0 + 2d/3d * y1 - 1d/12d * y2;
  }

  public static void main(String [] args) {
    List<Point> plot = new AdaptivePlot(x -> x * Math.sin(x), -2d * PI, 2d * PI)
        .computePlot(6, 0.005).getPlot();
    for (Point p : plot) {
      System.out.println(p.x + "\t" + p.y);
    }
  }
}
导入静态java.lang.Double.compare;
导入静态java.lang.Double.isFinite;
导入静态java.lang.Math.PI;
导入java.util.List;
导入java.util.SortedSet;
导入java.util.TreeSet;
导入java.util.function.DoubleUnaryOperator;
导入静态java.util.stream.Collectors.toList;
导入java.util.stream.DoubleStream;
公共类自适应图{
私人最终双一元运算符f;
私人决赛双a;
私人决赛双c;
私有最终分类集图=新树集((s,t)->比较(s.x,t.x));
公共自适应绘图(双一元运算符f、双a、双c){
这个。f=f;
这个a=a;
这个.c=c;
}
公共静态类点{
最终双x,y;
公共点(双x,双y){
这个.x=x;
这个。y=y;
}
}
公共自适应Plot computePlot(整数深度,双eps){
plot.clear();
点pa=点a;
点pc=点(c);
添加(pa);
绘图。添加(pc);
计算机绘图(pa、pc、深度、eps);
归还这个;
}
公共列表getPlot(){
返回plot.stream().collect(toList());
}
专用点(双x){
返回新点(x,f.applyasdoull(x));
}
私有void计算地块(点pa、点pc、整数深度、双eps){
点pb=点(0.5*(pa.x+pc.x));
点pa1=点(0.5*(pa.x+pb.x));
点pb1=点(0.5*(pb.x+pc.x));
绘图。添加(pb);
如果(深度>0&&
(振荡(pa.y,pa1.y,pb.y,pb1.y,pc.y)
||不平滑(pa.y、pa1.y、pb.y、pb1.y、pc.y、eps){
计算机绘图(pa、pb、深度-1、2*eps);
计算机绘图(pb、pc、深度-1、2*eps);
}
新增(第1页);
新增(pb1);
}
私有静态布尔振荡(
双ya,双ya1,双yb,双yb1,双yc){
返回等速振荡(ya,ya1,yb)
&&等速振荡(ya1,yb,yb1)
&&等速振荡(yb,yb1,yc);
}
专用静态布尔同位旋(双ya、双yb、双yc){
return!isFinite(ya)| |!isFinite(yb)| |!isFinite(yc)
||(yb>ya&&yb>yc)|(ybSubdivide left, middle, right:
    if DistancePointLine (middle, f(middle)), (left, f(left)), (right, f(right)) < Tolerance:
        DrawLine (left, f(left), (right, f(right))
    else
        Subdivide left, (left + middle) / 2, middle
        Subdivide middle, (middle + right) / 2, right