From b8fac5eb11bd0e9d9405ff91f8293ba65291a757 Mon Sep 17 00:00:00 2001 From: wyq Date: Thu, 10 Oct 2024 15:53:43 +0800 Subject: [PATCH] add fft package --- .../chart/graphic/TriMeshGraphic.java | 116 ++++- .../meteoinfo/chart/graphic/Triangle3D.java | 18 + .../java/org/meteoinfo/chart/jogl/GLPlot.java | 107 +--- .../org/meteoinfo/chart/jogl/Triangle.java | 38 -- .../jogl/tessellator/TriangleTessellator.java | 18 +- meteoinfo-lab/milconfig.xml | 40 +- .../pylib/mipylib/numeric/__init__$py.class | Bin 3891 -> 3954 bytes .../pylib/mipylib/numeric/__init__.py | 3 +- .../mipylib/numeric/core/numeric$py.class | Bin 152564 -> 153283 bytes .../pylib/mipylib/numeric/core/numeric.py | 13 + .../pylib/mipylib/numeric/fft/__init__.py | 2 + .../pylib/mipylib/numeric/fft/_fft.py | 127 +++++ .../pylib/mipylib/numeric/fft/_helper.py | 216 ++++++++ .../pylib/mipylib/plotlib/miplot$py.class | Bin 111039 -> 111045 bytes .../math/transform/ComplexTransform.java | 51 ++ .../org/meteoinfo/math/transform/FFT.java | 196 ++++++++ .../math/transform/FastFourierTransform.java | 462 ++++++++++++++++++ .../transform/FastFourierTransform2D.java | 67 +++ .../math/transform/TransformException.java | 37 ++ .../math/transform/TransformUtils.java | 237 +++++++++ .../org/meteoinfo/ndarray/ArrayComplex.java | 4 + pom.xml | 2 +- 22 files changed, 1598 insertions(+), 156 deletions(-) delete mode 100644 meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/Triangle.java create mode 100644 meteoinfo-lab/pylib/mipylib/numeric/fft/__init__.py create mode 100644 meteoinfo-lab/pylib/mipylib/numeric/fft/_fft.py create mode 100644 meteoinfo-lab/pylib/mipylib/numeric/fft/_helper.py create mode 100644 meteoinfo-math/src/main/java/org/meteoinfo/math/transform/ComplexTransform.java create mode 100644 meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FFT.java create mode 100644 meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FastFourierTransform.java create mode 100644 meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FastFourierTransform2D.java create mode 100644 meteoinfo-math/src/main/java/org/meteoinfo/math/transform/TransformException.java create mode 100644 meteoinfo-math/src/main/java/org/meteoinfo/math/transform/TransformUtils.java diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/TriMeshGraphic.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/TriMeshGraphic.java index 7b48e8f8..de3c11ad 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/TriMeshGraphic.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/TriMeshGraphic.java @@ -2,7 +2,10 @@ package org.meteoinfo.chart.graphic; import org.joml.Vector3f; import org.meteoinfo.chart.jogl.Transform; +import org.meteoinfo.common.Extent; import org.meteoinfo.common.Extent3D; +import org.meteoinfo.geometry.graphic.Graphic; +import org.meteoinfo.geometry.legend.ColorBreak; import org.meteoinfo.geometry.legend.LegendManage; import org.meteoinfo.geometry.colors.TransferFunction; import org.meteoinfo.geometry.graphic.GraphicCollection3D; @@ -17,7 +20,7 @@ import java.awt.*; import java.util.*; import java.util.List; -public class TriMeshGraphic extends GraphicCollection3D { +public class TriMeshGraphic extends Graphic { protected Logger logger = LoggerFactory.getLogger("TriMeshGraphic"); protected float[] vertexPosition; @@ -30,6 +33,10 @@ public class TriMeshGraphic extends GraphicCollection3D { protected boolean edgeInterp; protected boolean mesh; protected boolean normalLoaded = false; + protected Extent extent; + protected LegendScheme legendScheme; + protected boolean singleLegend = true; + protected ColorBreak legendBreak; /** * Constructor @@ -239,6 +246,46 @@ public class TriMeshGraphic extends GraphicCollection3D { updateExtent(); } + /** + * Set triangles + * @param vertexData The triangle vertex array + */ + public void setTriangles(List triangles) { + LinkedHashMap map = new LinkedHashMap(); + int n = triangles.size(); + this.vertexIndices = new int[n]; + Vector3f vector3f; + int idx = 0, vertexIdx = 0, triangleIdx = 0, index, ii; + List idxList = new ArrayList<>(); + for (int i = 0; i < n; i++) { + Triangle3D triangle = triangles.get(i); + for (int j = 0; j < 3; j++) { + vector3f = new Vector3f(triangle.getPoint(j)); + if (map.containsKey(vector3f)) { + index = map.get(vector3f); + vertexIndices[vertexIdx] = index; + } else { + vertexIndices[vertexIdx] = idx; + map.put(vector3f, idx++); + } + + vertexIdx += 1; + } + triangleIdx += 1; + } + + this.vertexPosition = new float[map.size() * 3]; + idx = 0; + for (Map.Entry entry : map.entrySet()) { + vector3f = entry.getKey(); + vertexPosition[idx++] = vector3f.x; + vertexPosition[idx++] = vector3f.y; + vertexPosition[idx++] = vector3f.z; + } + + updateExtent(); + } + /** * Set triangles * @param vertexData The triangle vertex @@ -430,12 +477,77 @@ public class TriMeshGraphic extends GraphicCollection3D { } } + /** + * Get extent + * + * @return The extent + */ @Override + public Extent getExtent() { + return extent; + } + + /** + * Set extent + * + * @param value Extent + */ + @Override + public void setExtent(Extent value) { + this.extent = value; + } + + /** + * Get legend scheme + * @return Legend scheme + */ + public LegendScheme getLegendScheme() { + return this.legendScheme; + } + + /** + * Set legend scheme + * @param ls Legend scheme + */ public void setLegendScheme(LegendScheme ls) { - super.setLegendScheme(ls); + this.legendScheme = ls; updateVertexColor(); } + /** + * Get is single legend or not + * @return Boolean + */ + public boolean isSingleLegend() { + return this.singleLegend; + } + + /** + * Set single legend or not + * @param value Boolean + */ + public void setSingleLegend(boolean value) { + this.singleLegend = value; + } + + /** + * Get legend break + * + * @return Legend break + */ + public ColorBreak getLegendBreak() { + return this.legendBreak; + } + + /** + * Set legend break + * + * @param value Legend break + */ + public void setLegendBreak(ColorBreak value) { + this.legendBreak = value; + } + /** * Set transfer function * @param transferFunction Transfer function diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/Triangle3D.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/Triangle3D.java index 291b11da..12e665d6 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/Triangle3D.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/Triangle3D.java @@ -26,6 +26,24 @@ public class Triangle3D { this.pointC = c; } + /** + * Get point by index + * @param index The index + * @return The point + */ + public Vector3f getPoint(int index) { + switch (index) { + case 0: + return pointA; + case 1: + return pointB; + case 2: + return pointC; + default: + return null; + } + } + /** * Get point a * @return Point a diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java index 7a72144f..e509beb5 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java @@ -2676,6 +2676,18 @@ public class GLPlot extends Plot { } } + protected void drawTriMeshGraphic(GL2 gl, TriMeshGraphic graphic) { + if (!this.renderMap.containsKey(graphic)) { + renderMap.put(graphic, new TriMeshRender(gl, graphic)); + } + TriMeshRender triMeshRender = (TriMeshRender) renderMap.get(graphic); + triMeshRender.setTransform(this.transform, this.alwaysUpdateBuffers); + triMeshRender.setOrthographic(this.orthographic); + triMeshRender.setLighting(this.lighting); + triMeshRender.updateMatrix(); + triMeshRender.draw(); + } + protected void drawGraphics(GL2 gl, Graphic graphic) { boolean lightEnabled = this.lighting.isEnable(); if (graphic instanceof GraphicCollection3D) { @@ -2718,15 +2730,7 @@ public class GLPlot extends Plot { modelRender.setRotateModelView(this.modelViewMatrixR); modelRender.draw(); } else { - if (!this.renderMap.containsKey(graphic)) { - renderMap.put(graphic, new TriMeshRender(gl, (TriMeshGraphic) graphic)); - } - TriMeshRender triMeshRender = (TriMeshRender) renderMap.get(graphic); - triMeshRender.setTransform(this.transform, this.alwaysUpdateBuffers); - triMeshRender.setOrthographic(this.orthographic); - triMeshRender.setLighting(this.lighting); - triMeshRender.updateMatrix(); - triMeshRender.draw(); + drawTriMeshGraphic(gl, (TriMeshGraphic) graphic); } } else if (graphic instanceof VolumeGraphic) { try { @@ -2762,7 +2766,7 @@ public class GLPlot extends Plot { } } if (isDraw) { - switch (graphic.getGraphicN(0).getShape().getShapeType()) { + switch (graphic.getGraphicN(0).getShapeType()) { case POINT_Z: if (!this.renderMap.containsKey(graphic)) { renderMap.put(graphic, new PointRender(gl, (GraphicCollection3D) graphic)); @@ -2815,7 +2819,11 @@ public class GLPlot extends Plot { default: for (int i = 0; i < graphic.getNumGraphics(); i++) { Graphic gg = graphic.getGraphicN(i); - this.drawGraphic(gl, gg); + if (gg instanceof TriMeshGraphic) { + this.drawTriMeshGraphic(gl, (TriMeshGraphic) gg); + } else { + this.drawGraphic(gl, gg); + } } break; } @@ -3339,83 +3347,6 @@ public class GLPlot extends Plot { } } - private void drawPolygon_bak(GL2 gl, PolygonZ aPG, PolygonBreak aPGB) { - PointZ p; - if (aPGB.isDrawFill() && aPGB.getColor().getAlpha() > 0) { - gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL); - gl.glPolygonOffset(1.0f, 1.0f); - - float[] rgba = aPGB.getColor().getRGBComponents(null); - gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]); - - try { - GLUtessellator tobj = glu.gluNewTess(); - //TessCallback tessCallback = new TessCallback(gl, glu); - - glu.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, tessCallback); - glu.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, tessCallback); - glu.gluTessCallback(tobj, GLU.GLU_TESS_END, tessCallback); - glu.gluTessCallback(tobj, GLU.GLU_TESS_ERROR, tessCallback); - //glu.gluTessCallback(tobj, GLU.GLU_TESS_COMBINE, tessCallback); - - //gl.glNewList(startList, GL2.GL_COMPILE); - //gl.glShadeModel(GL2.GL_FLAT); - glu.gluTessBeginPolygon(tobj, null); - glu.gluTessBeginContour(tobj); - double[] v; - for (int i = 0; i < aPG.getOutLine().size() - 1; i++) { - p = ((java.util.List) aPG.getOutLine()).get(i); - v = transform.transform(p); - glu.gluTessVertex(tobj, v, 0, v); - } - glu.gluTessEndContour(tobj); - if (aPG.hasHole()) { - for (int i = 0; i < aPG.getHoleLineNumber(); i++) { - glu.gluTessBeginContour(tobj); - for (int j = 0; j < aPG.getHoleLine(i).size() - 1; j++) { - p = ((java.util.List) aPG.getHoleLine(i)).get(j); - v = transform.transform(p); - glu.gluTessVertex(tobj, v, 0, v); - } - glu.gluTessEndContour(tobj); - } - } - glu.gluTessEndPolygon(tobj); - //gl.glEndList(); - glu.gluDeleteTess(tobj); - - //gl.glCallList(startList); - } catch (Exception e) { - e.printStackTrace(); - } - } - if (aPGB.isDrawOutline()) { - float[] rgba = aPGB.getOutlineColor().getRGBComponents(null); - gl.glLineWidth(aPGB.getOutlineSize() * this.dpiScale); - gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]); - gl.glBegin(GL2.GL_LINE_STRIP); - for (int i = 0; i < aPG.getOutLine().size(); i++) { - p = ((java.util.List) aPG.getOutLine()).get(i); - gl.glVertex3f(transform.transform_x((float) p.X), transform.transform_y((float) p.Y), transform.transform_z((float) p.Z)); - } - gl.glEnd(); - - if (aPG.hasHole()) { - java.util.List newPList; - gl.glBegin(GL2.GL_LINE_STRIP); - for (int h = 0; h < aPG.getHoleLines().size(); h++) { - newPList = (java.util.List) aPG.getHoleLines().get(h); - for (int j = 0; j < newPList.size(); j++) { - p = newPList.get(j); - gl.glVertex3f(transform.transform_x((float) p.X), transform.transform_y((float) p.Y), transform.transform_z((float) p.Z)); - } - } - gl.glEnd(); - } - gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL); - } - } - private void drawConvexPolygon(GL2 gl, PolygonZ aPG, PolygonBreak aPGB) { PointZ p; if (aPGB.isDrawFill()) { diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/Triangle.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/Triangle.java deleted file mode 100644 index da0a38c7..00000000 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/Triangle.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.meteoinfo.chart.jogl; - -import org.meteoinfo.geometry.shape.PointZ; - -import java.util.ArrayList; -import java.util.List; - -public class Triangle { - private static final long serialVersionUID = 1; - - public final PointZ p1; - public final PointZ p2; - public final PointZ p3; - - public Triangle(PointZ p1, PointZ p2, PointZ p3) { - super(); - this.p1 = p1; - this.p2 = p2; - this.p3 = p3; - } - - public List getPoints() { - List points = new ArrayList<>(); - points.add(p1); - points.add(p2); - points.add(p3); - return points; - } - - public PointZ[] getPointArray() { - return new PointZ[]{p1, p2, p3}; - } - - @Override - public String toString() { - return "Triangle [p1=" + p1 + ", p2=" + p2 + ", p3=" + p3 + "]"; - } -} diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/tessellator/TriangleTessellator.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/tessellator/TriangleTessellator.java index 4f3b9732..7f1f6d36 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/tessellator/TriangleTessellator.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/tessellator/TriangleTessellator.java @@ -4,9 +4,10 @@ import com.jogamp.opengl.GL2; import com.jogamp.opengl.glu.GLU; import com.jogamp.opengl.glu.GLUtessellator; import com.jogamp.opengl.glu.GLUtessellatorCallbackAdapter; -import org.meteoinfo.chart.jogl.Triangle; +import org.meteoinfo.chart.graphic.Triangle3D; import org.meteoinfo.geometry.shape.PointZ; import org.meteoinfo.geometry.shape.PolygonZ; +import org.joml.Vector3f; import java.util.ArrayList; import java.util.List; @@ -37,7 +38,7 @@ public class TriangleTessellator { * Throws {@link TesselationException} if the tessellation was * unsuccessful, most commonly due to ambiguous shapes */ - public List getTriangles(PolygonZ polygon) + public List getTriangles(PolygonZ polygon) throws TesselationException { makeTriangles(polygon); @@ -83,7 +84,7 @@ public class TriangleTessellator { } public interface TessellatorListener { - public void onTesselationDone(List triangles); + public void onTesselationDone(List triangles); public void onTesselationError(TesselationException err); } @@ -163,9 +164,9 @@ public class TriangleTessellator { */ class TessellationCallback extends GLUtessellatorCallbackAdapter { - protected List triangles = new ArrayList<>(); + protected List triangles = new ArrayList<>(); - private PointZ p1, p2, p3; + private Vector3f p1, p2, p3; private int geometricPrimitiveType; @@ -178,7 +179,8 @@ public class TriangleTessellator { } public void vertex(Object vertexData) { - PointZ thisPoint = new PointZ((double[]) vertexData); + double[] vertex = (double[]) vertexData; + Vector3f thisPoint = new Vector3f((float) vertex[0], (float) vertex[1], (float) vertex[2]); switch (geometricPrimitiveType) { case GL2.GL_TRIANGLE_FAN: @@ -187,7 +189,7 @@ public class TriangleTessellator { } else if (p2 == null) { p2 = thisPoint; } else { - triangles.add(new Triangle(p1, p2, thisPoint)); + triangles.add(new Triangle3D(p1, p2, thisPoint)); p2 = thisPoint; } break; @@ -201,7 +203,7 @@ public class TriangleTessellator { p3 = thisPoint; if (p1 != null) { - triangles.add(new Triangle(p1, p2, p3)); + triangles.add(new Triangle3D(p1, p2, p3)); } } break; diff --git a/meteoinfo-lab/milconfig.xml b/meteoinfo-lab/milconfig.xml index 8afd01e1..3f89eb8b 100644 --- a/meteoinfo-lab/milconfig.xml +++ b/meteoinfo-lab/milconfig.xml @@ -1,32 +1,36 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + + + - + + + @@ -34,5 +38,5 @@
- + diff --git a/meteoinfo-lab/pylib/mipylib/numeric/__init__$py.class b/meteoinfo-lab/pylib/mipylib/numeric/__init__$py.class index c31ace0eda88fbace479feca65f0011f82e9ac26..31b67b07d42eca8e7361ce91e81fb204da22fac9 100644 GIT binary patch delta 1195 zcmZuw-%}G;6#i~@*}Yj;CR_|sXF#TQGD@IS##&0jRIOg7%IZ^Yf3NjLOmvayCOBOQsHYvNsb*;mEh zqi@jP?Pe3{xQSNzw{H!9A0Jp)h1D{o)dkzI)i(?67ot>VbY-6c20eq=3@$=P5?E9#J}G1`Na+J-$A-A8SF zjsprEln0H)>}z?^=;eoS*uppXRz5S<^6&A3()dxXHyw6V{%-d1UvS*QF`SU)d~it; zrx+H^9i?k1osRBF#Y~)$S9n*+c;?Z>1NoFsvdeP9-?KbPlQ2xc2d)O8=dDy-k~PRO z@-zGf=qANW0PVtbmDS@LE6almOTP!ZvtAQT-SuI;fCYpboAt10dQH!xk`WA)6Y(yZ zBLVv!S|XaLDPT=^L>Hl=EfKF5ts&khT2m}7S}tmfHeiT1Jz$ErJitX`0b`^Q@QW3m z%MvR+Fi$ioFoTae!(x5C>Y+0n2vQ4l&7(tf=CQR-wL_h&Zn|gbd2HVv4pjVy7#5`} zi88l(goUpN+h(Q0qWa&A%2$lJwd(#^%l9XCO(A-VerJX&^xcUHIX=H_tc-9uogkeG zIt%Hj7okn@UV{F9^P!P9R6B!s?KB28jh;76yQ!{_%<jQ69*!}A7J9avu7_J{Q>H$ZW7!M)!$d|eQ&9LRo^ocndZkI?>_*Tg%^Ehr$_d9 z#%psK!m?Kj`*h4#G{D1L#{&7)Q)ZN4p^0KFlGAxXs1#);=3xngZdrAWjh1C#schvX zCCjkfgoa86U+hpk*1XHWN`@56+8vA6#pCUk6~JnxDMFQFKM-s0uv?l{hBfjVug_kG z^~$?}ayM9wbsce=fmH@ji;aHDyh%1`=_#8Tw5pco7{dg4jb|8JvCWSy*e(-V@whq$ zo|{)2Kq=}??8L6?-Z?3I0%*W)KWyxki@mw5N!s4mq6Muc#kB>%(5RdpGS62TB5^5p zn6y6a7udha^nonsvO;Nt!}Z4vQMu!F5`-cOSmdC zj3#d5y1Z;;u^aN0vCil&sz_urWXd;2NKW*hVGreFf6Yvr`ee8X4-C~O4JNEwBJ>e5 zvNJpe_-FEa3GGCVS>x#vNf<+z7Y(KiqbSNn_8=Cok7^>vL9dwXpifM3P#3ul8Y17p zpifM5i7sZi#1OMw;uo`BVv0gV22frV5fxLD2(zk4FqH~NL{~);E5xcKstZ&Xn%l(M z5q%Oh8zaGxqZ}1R#8?$XhH6)k_KzU*PeJ-$g3(fwMZxd2JnsW7 zjakwqh^NvD(MqG0PJSA&`%mc!i~%QWPW}L5`3dafd>Db%1rgEAIq#=EV|-i?gJ@46 z-bYH6pRUD7e2|clP3uQo)du`Hh(jaZZ1CldJ9P+lR2&|_(Ik!y;xx_RoSV##^SCe~ z7mk+3F6=?{xak;raZUN?LY!t;}^il)P6x`IcbCu_U(Cmrkm9=cD@ fDB6PucuqKpgtw@`?MmFiT}4*l9_}mBfd{_;66oHa diff --git a/meteoinfo-lab/pylib/mipylib/numeric/__init__.py b/meteoinfo-lab/pylib/mipylib/numeric/__init__.py index f8520819..35e7e56f 100644 --- a/meteoinfo-lab/pylib/mipylib/numeric/__init__.py +++ b/meteoinfo-lab/pylib/mipylib/numeric/__init__.py @@ -14,9 +14,10 @@ from . import signal from . import spatial from . import special from . import integrate +from . import fft __all__ = ['linalg', 'fitting', 'random', 'ma', 'stats', 'interpolate', 'optimize', 'signal', 'spatial', - 'special', 'integrate'] + 'special', 'integrate', 'fft'] __all__.extend(['__version__']) __all__.extend(core.__all__) __all__.extend(lib.__all__) diff --git a/meteoinfo-lab/pylib/mipylib/numeric/core/numeric$py.class b/meteoinfo-lab/pylib/mipylib/numeric/core/numeric$py.class index b77d1c3ac7cd3ff4ba7313d3563c01c271c1af68..ea4a4d355e44e1f9d129e62f502698eb189894a6 100644 GIT binary patch literal 153283 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

}UQS4;z$@U&2GFiHCob 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

{q8@rm`7srgbV!I-ejqFIiw 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Ν!+H3xI+&aH&Or%{s;9K@wO(l zzk^HUgV_nC_fk1-o4f%PKOF?=Jqnk*@pjL)Os82cbqP@e(?majvz`9^5y($I z;HMuLr0=Iwe=y`oxl$ufJ%rQiuab!)TAa3hK;5j(DBtTgQqYf(4F4&(vinOhBJTQDj-`Qf@+Q-U3sDqzc;vFLz8taNS{?%@ zG!Ox|^3w-`^r*rLi7H6apy2!=nB+biS<7H*VQ{g?U)TV~gbKlg#REkBllC%gp-*h= z%Wk*Op=K7>wzyv+?&U&cajwQa4~iEvFcc&)g6am6 z4QWY-XwSo+2S6Csek}BSnmekyaNeaqkt`!1j$b61epHaYR#7nrR7jVLZEUc?*l$mF%6Rp7Q33n3LE)Hn5s^a5>I8<%}f3#(KBqElQ?%A|pV;8n_`H(eAj10?9 zBV!EisA`mh^a(kYnZ6E6{$iDx@6spr&0~Y~Q6-M+p|2UTz->PhIyiZeD>pC~>X6pi zgk=k%BFDz%I2#O4e)@(WJ+DnY9)~qc9XU?1SSaUbkr(QK4C$sSwq&wRfdt_0MsjV4 zY=D>!&tEuYChB{>aaSWR9ThMs@DH}{uy!$si|vaHb$Ggg*SH{xj5Y-MJXWh-0ErtO z1PX9P{HfITt1fmr7Z=EJkQGV`S+y_hz4?KL^Lc9ahM z^g@t6qu{(L0?s19S@4oWmkmxH{P}i*lQK?)zC%1f4{inh556@AZE{y32i&wcr*xsZ zTMY+r$~$_mFDGNM7fuQl!(;MtKC#DVdywDz=4VYbzJ>vpAslhf)A%I#(_j z$N~Y5(3w*iX&B($-pLKqgvr}yqqp{2-4F}I#y0{*p_E~GM(CN*SR+-9#C_>_r7j&Z zOvwmJd-BMVVsp8IliyfF{2*7YEy3zPKN8JRU_=Fs-;}0e1BX1YW;drXsFzgfN3)mq z5%}TCHVhx`cixXE%9h>%Cn6vmdcUUBZXP08pd4yr5p#v(@UT(xMop++f*ou6H-TG1 z>-33mIFaO4jv*Guj#*?QbxUe^j8Z0U-C z@)432*Nkirx-cWvpvaoos2I+N0=>=?C$vhjitxA4&%G>)60f=F9Ld!S_DdB>n z$lJ!GcfxFW;WU>l7NRhjxF^}M1;|@zZ$ULmbYrT?ZW7pow$i`?CnLc>WGFZQhPV*K znjol)sgD?$KxL=ek(Qu-RBEd4n8T4&^X<@h_xl(wGm*o`om_hd7ujy`N*Zhnv|)Hg z79kQ&)}^&*NDHW=(SmR#l^}P5of_0P@vt!&KAaeOaW~GS<5(Kaf6|P*wH(VbmJfq% zDR@J0tyij;H*5<~X%U$eZ-6d012br;OIu>1BJoO?474Qp;3ImeN`v-A+&DpwP{D#= zhYYZi&AMso5~>bV8?sSl>v8?&gfKD~grb20DtWcIlGGNPIMu(2GrDju*##p*O9AP^a8%$a1w~LJXrP9V zB&G04P@8IE`y^p+3{t9BtQ!3Slh<*5G$D|=KmUPjB zAvU2*SS=c&6r0{610q6c5~+bYDdCrS*)
I!J%v@P!omNjHB&H*UgqR=@-i0Er0 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 dtype: + dtype = o.dtype + a = zeros(shape, dtype=dtype) + for i in range(len(object)): + a[i] = object[i] elif miutil.iscomplex(object): a = NDArray(JythonUtil.toComplexArray(object)) diff --git a/meteoinfo-lab/pylib/mipylib/numeric/fft/__init__.py b/meteoinfo-lab/pylib/mipylib/numeric/fft/__init__.py new file mode 100644 index 00000000..897f8c75 --- /dev/null +++ b/meteoinfo-lab/pylib/mipylib/numeric/fft/__init__.py @@ -0,0 +1,2 @@ +from ._fft import * +from ._helper import * \ No newline at end of file diff --git a/meteoinfo-lab/pylib/mipylib/numeric/fft/_fft.py b/meteoinfo-lab/pylib/mipylib/numeric/fft/_fft.py new file mode 100644 index 00000000..eba2ed8a --- /dev/null +++ b/meteoinfo-lab/pylib/mipylib/numeric/fft/_fft.py @@ -0,0 +1,127 @@ +from org.meteoinfo.math.transform import FastFourierTransform, FastFourierTransform2D + +from .. import core as _nx + +__all__ = ['fft', 'ifft', 'fft2', 'ifft2'] + + +def fft(a): + """ + Compute the one-dimensional discrete Fourier Transform. + + This function computes the one-dimensional *n*-point discrete Fourier + Transform (DFT) with the efficient Fast Fourier Transform (FFT) + algorithm [CT]. + + Parameters + ---------- + a : array_like + Input array, can be complex. + + Returns + ------- + out : complex ndarray + The truncated or zero-padded input, transformed along the axis + indicated by `axis`, or the last one if `axis` is not specified. + + References + ---------- + .. [CT] Cooley, James W., and John W. Tukey, 1965, "An algorithm for the + machine calculation of complex Fourier series," *Math. Comput.* + 19: 297-301. + """ + a = _nx.asarray(a) + jfft = FastFourierTransform() + r = jfft.apply(a._array) + + return _nx.NDArray(r) + +def ifft(a): + """ + Compute the one-dimensional inverse discrete Fourier Transform. + + This function computes the inverse of the one-dimensional *n*-point + discrete Fourier transform computed by `fft`. In other words, + ``ifft(fft(a)) == a`` to within numerical accuracy. + For a general description of the algorithm and definitions, + see `numpy.fft`. + + The input should be ordered in the same way as is returned by `fft`, + i.e., + + Parameters + ---------- + a : array_like + Input array, can be complex. + + Returns + ------- + out : complex ndarray + The truncated or zero-padded input, transformed along the axis + indicated by `axis`, or the last one if `axis` is not specified. + """ + a = _nx.asarray(a) + jfft = FastFourierTransform(True) + r = jfft.apply(a._array) + + return _nx.NDArray(r) + +def fft2(a): + """ + Compute the 2-dimensional discrete Fourier Transform. + + This function computes the *n*-dimensional discrete Fourier Transform + over any axes in an *M*-dimensional array by means of the + Fast Fourier Transform (FFT). By default, the transform is computed over + the last two axes of the input array, i.e., a 2-dimensional FFT. + + Parameters + ---------- + a : array_like + Input array, can be complex + + Returns + ------- + out : complex ndarray + The truncated or zero-padded input, transformed along the axes + indicated by `axes`, or the last two axes if `axes` is not given. + """ + a = _nx.asarray(a) + jfft = FastFourierTransform2D() + r = jfft.apply(a._array) + + return _nx.NDArray(r) + +def ifft2(a, s=None, axes=(-2, -1), norm=None, out=None): + """ + Compute the 2-dimensional inverse discrete Fourier Transform. + + This function computes the inverse of the 2-dimensional discrete Fourier + Transform over any number of axes in an M-dimensional array by means of + the Fast Fourier Transform (FFT). In other words, ``ifft2(fft2(a)) == a`` + to within numerical accuracy. By default, the inverse transform is + computed over the last two axes of the input array. + + The input, analogously to `ifft`, should be ordered in the same way as is + returned by `fft2`, i.e. it should have the term for zero frequency + in the low-order corner of the two axes, the positive frequency terms in + the first half of these axes, the term for the Nyquist frequency in the + middle of the axes and the negative frequency terms in the second half of + both axes, in order of decreasingly negative frequency. + + Parameters + ---------- + a : array_like + Input array, can be complex. + + Returns + ------- + out : complex ndarray + The truncated or zero-padded input, transformed along the axes + indicated by `axes`, or the last two axes if `axes` is not given. + """ + a = _nx.asarray(a) + jfft = FastFourierTransform2D(True) + r = jfft.apply(a._array) + + return _nx.NDArray(r) diff --git a/meteoinfo-lab/pylib/mipylib/numeric/fft/_helper.py b/meteoinfo-lab/pylib/mipylib/numeric/fft/_helper.py new file mode 100644 index 00000000..271acdea --- /dev/null +++ b/meteoinfo-lab/pylib/mipylib/numeric/fft/_helper.py @@ -0,0 +1,216 @@ +""" +Discrete Fourier Transforms - _helper.py + +""" +from ..core import empty, arange, asarray, roll + +# Created by Pearu Peterson, September 2002 + +__all__ = ['fftshift', 'ifftshift', 'fftfreq', 'rfftfreq'] + +integer_types = (int) + + +def _fftshift_dispatcher(x, axes=None): + return (x,) + + +def fftshift(x, axes=None): + """ + Shift the zero-frequency component to the center of the spectrum. + + This function swaps half-spaces for all axes listed (defaults to all). + Note that ``y[0]`` is the Nyquist component only if ``len(x)`` is even. + + Parameters + ---------- + x : array_like + Input array. + axes : int or shape tuple, optional + Axes over which to shift. Default is None, which shifts all axes. + + Returns + ------- + y : ndarray + The shifted array. + + See Also + -------- + ifftshift : The inverse of `fftshift`. + + Examples + -------- + >>> freqs = np.fft.fftfreq(10, 0.1) + >>> freqs + array([ 0., 1., 2., ..., -3., -2., -1.]) + >>> np.fft.fftshift(freqs) + array([-5., -4., -3., -2., -1., 0., 1., 2., 3., 4.]) + + Shift the zero-frequency component only along the second axis: + + >>> freqs = np.fft.fftfreq(9, d=1./9).reshape(3, 3) + >>> freqs + array([[ 0., 1., 2.], + [ 3., 4., -4.], + [-3., -2., -1.]]) + >>> np.fft.fftshift(freqs, axes=(1,)) + array([[ 2., 0., 1.], + [-4., 3., 4.], + [-1., -3., -2.]]) + + """ + x = asarray(x) + if axes is None: + axes = tuple(range(x.ndim)) + shift = [dim // 2 for dim in x.shape] + elif isinstance(axes, integer_types): + shift = x.shape[axes] // 2 + else: + shift = [x.shape[ax] // 2 for ax in axes] + + return roll(x, shift, axes) + + +def ifftshift(x, axes=None): + """ + The inverse of `fftshift`. Although identical for even-length `x`, the + functions differ by one sample for odd-length `x`. + + Parameters + ---------- + x : array_like + Input array. + axes : int or shape tuple, optional + Axes over which to calculate. Defaults to None, which shifts all axes. + + Returns + ------- + y : ndarray + The shifted array. + + See Also + -------- + fftshift : Shift zero-frequency component to the center of the spectrum. + + Examples + -------- + >>> freqs = np.fft.fftfreq(9, d=1./9).reshape(3, 3) + >>> freqs + array([[ 0., 1., 2.], + [ 3., 4., -4.], + [-3., -2., -1.]]) + >>> np.fft.ifftshift(np.fft.fftshift(freqs)) + array([[ 0., 1., 2.], + [ 3., 4., -4.], + [-3., -2., -1.]]) + + """ + x = asarray(x) + if axes is None: + axes = tuple(range(x.ndim)) + shift = [-(dim // 2) for dim in x.shape] + elif isinstance(axes, integer_types): + shift = -(x.shape[axes] // 2) + else: + shift = [-(x.shape[ax] // 2) for ax in axes] + + return roll(x, shift, axes) + + +def fftfreq(n, d=1.0): + """ + Return the Discrete Fourier Transform sample frequencies. + + The returned float array `f` contains the frequency bin centers in cycles + per unit of the sample spacing (with zero at the start). For instance, if + the sample spacing is in seconds, then the frequency unit is cycles/second. + + Given a window length `n` and a sample spacing `d`:: + + f = [0, 1, ..., n/2-1, -n/2, ..., -1] / (d*n) if n is even + f = [0, 1, ..., (n-1)/2, -(n-1)/2, ..., -1] / (d*n) if n is odd + + Parameters + ---------- + n : int + Window length. + d : scalar, optional + Sample spacing (inverse of the sampling rate). Defaults to 1. + + Returns + ------- + f : ndarray + Array of length `n` containing the sample frequencies. + + Examples + -------- + >>> signal = np.array([-2, 8, 6, 4, 1, 0, 3, 5], dtype=float) + >>> fourier = np.fft.fft(signal) + >>> n = signal.size + >>> timestep = 0.1 + >>> freq = np.fft.fftfreq(n, d=timestep) + >>> freq + array([ 0. , 1.25, 2.5 , ..., -3.75, -2.5 , -1.25]) + + """ + if not isinstance(n, integer_types): + raise ValueError("n should be an integer") + val = 1.0 / (n * d) + results = empty(n, dtype='int') + N = (n-1)//2 + 1 + p1 = arange(0, N, dtype='int') + results[:N] = p1 + p2 = arange(-(n//2), 0, dtype='int') + results[N:] = p2 + return results * val + + +def rfftfreq(n, d=1.0): + """ + Return the Discrete Fourier Transform sample frequencies + (for usage with rfft, irfft). + + The returned float array `f` contains the frequency bin centers in cycles + per unit of the sample spacing (with zero at the start). For instance, if + the sample spacing is in seconds, then the frequency unit is cycles/second. + + Given a window length `n` and a sample spacing `d`:: + + f = [0, 1, ..., n/2-1, n/2] / (d*n) if n is even + f = [0, 1, ..., (n-1)/2-1, (n-1)/2] / (d*n) if n is odd + + Unlike `fftfreq` (but like `scipy.fftpack.rfftfreq`) + the Nyquist frequency component is considered to be positive. + + Parameters + ---------- + n : int + Window length. + d : scalar, optional + Sample spacing (inverse of the sampling rate). Defaults to 1. + + Returns + ------- + f : ndarray + Array of length ``n//2 + 1`` containing the sample frequencies. + + Examples + -------- + >>> signal = np.array([-2, 8, 6, 4, 1, 0, 3, 5, -3, 4], dtype=float) + >>> fourier = np.fft.rfft(signal) + >>> n = signal.size + >>> sample_rate = 100 + >>> freq = np.fft.fftfreq(n, d=1./sample_rate) + >>> freq + array([ 0., 10., 20., ..., -30., -20., -10.]) + >>> freq = np.fft.rfftfreq(n, d=1./sample_rate) + >>> freq + array([ 0., 10., 20., 30., 40., 50.]) + + """ + if not isinstance(n, integer_types): + raise ValueError("n should be an integer") + val = 1.0/(n*d) + N = n//2 + 1 + results = arange(0, N, dtype='int') + return results * val \ No newline at end of file diff --git a/meteoinfo-lab/pylib/mipylib/plotlib/miplot$py.class b/meteoinfo-lab/pylib/mipylib/plotlib/miplot$py.class index 08ffe5951ac7274f39d821a8ea8720a6ae673d5a..eb8d6c84dfb55104f2b034f38d6311c35f9900e9 100644 GIT binary patch delta 17773 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 delta 17880 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 diff --git a/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/ComplexTransform.java b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/ComplexTransform.java new file mode 100644 index 00000000..b2c2bc1c --- /dev/null +++ b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/ComplexTransform.java @@ -0,0 +1,51 @@ +package org.meteoinfo.math.transform; + +import org.meteoinfo.ndarray.Array; +import org.meteoinfo.ndarray.Complex; + +import java.util.function.DoubleUnaryOperator; +import java.util.function.UnaryOperator; + +/** + * {@link Complex} transform. + *

+ * Such transforms include {@link FastSineTransform sine transform}, + * {@link FastCosineTransform cosine transform} or {@link + * FastHadamardTransform Hadamard transform}. + */ +public interface ComplexTransform extends UnaryOperator { + /** + * Returns the transform of the specified data set. + * + * @param f the data array to be transformed (signal). + * @return the transformed array (spectrum). + * @throws IllegalArgumentException if the transform cannot be performed. + */ + Array apply(Array f); + + /** + * Returns the transform of the specified data set. + * + * @param f the data array to be transformed (signal). + * @return the transformed array (spectrum). + * @throws IllegalArgumentException if the transform cannot be performed. + */ + Array apply(double[] f); + + /** + * Returns the transform of the specified function. + * + * @param f Function to be sampled and transformed. + * @param min Lower bound (inclusive) of the interval. + * @param max Upper bound (exclusive) of the interval. + * @param n Number of sample points. + * @return the result. + * @throws IllegalArgumentException if the transform cannot be performed. + */ + default Array apply(DoubleUnaryOperator f, + double min, + double max, + int n) { + return apply(TransformUtils.sample(f, min, max, n)); + } +} diff --git a/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FFT.java b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FFT.java new file mode 100644 index 00000000..bc346897 --- /dev/null +++ b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FFT.java @@ -0,0 +1,196 @@ +package org.meteoinfo.math.transform; + +import org.meteoinfo.ndarray.Array; +import org.meteoinfo.ndarray.Complex; +import org.meteoinfo.ndarray.DataType; + +public class FFT { + + // compute the fast fourier transform of x[], assuming its length is a power of 2 + public static Complex[] fft(Complex[] x) { + int N = x.length; + + // base case + if (N == 1) return new Complex[] { x[0] }; + + // radix 2 Cooley-Tukey FFT + if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2"); } + + // fft of even terms + Complex[] even = new Complex[N/2]; + for (int k = 0; k < N/2; k++) { + even[k] = x[2*k]; + } + Complex[] q = fft(even); + + // fft of odd terms + Complex[] odd = even; // reuse the array + for (int k = 0; k < N/2; k++) { + odd[k] = x[2*k + 1]; + } + Complex[] r = fft(odd); + + // combine + Complex[] y = new Complex[N]; + for (int k = 0; k < N/2; k++) { + double kth = -2 * k * Math.PI / N; + Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); + y[k] = q[k].add(wk.multiply(r[k])); + y[k + N/2] = q[k].subtract(wk.multiply(r[k])); + } + return y; + } + + // compute the FFT of x[], assuming its length is a power of 2 + public static Array fft(Array x) { + x = x.copyIfView(); + + int N = (int) x.getSize(); + + // base case + if (N == 1) return x.copy(); + + // radix 2 Cooley-Tukey FFT + if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2"); } + + // fft of even terms + Complex[] even = new Complex[N / 2]; + for (int k = 0; k < N / 2; k++) { + even[k] = x.getComplex(2 * k); + } + Complex[] q = fft(even); + + // fft of odd terms + Complex[] odd = even; // reuse the array + for (int k = 0; k < N/2; k++) { + odd[k] = x.getComplex(2 * k + 1); + } + Complex[] r = fft(odd); + + // combine + Array y = Array.factory(DataType.COMPLEX, new int[]{N}); + for (int k = 0; k < N / 2; k++) { + double kth = -2 * k * Math.PI / N; + Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); + y.setComplex(k, q[k].add(wk.multiply(r[k]))); + y.setComplex(k + N / 2, q[k].subtract(wk.multiply(r[k]))); + } + return y; + } + + // compute the inverse FFT of x[], assuming its length is a power of 2 + public static Complex[] ifft(Complex[] x) { + int N = x.length; + Complex[] y = new Complex[N]; + + // take conjugate + for (int i = 0; i < N; i++) { + y[i] = x[i].conj(); + } + + // compute forward FFT + y = fft(y); + + // take conjugate again + for (int i = 0; i < N; i++) { + y[i] = y[i].conj(); + } + + // divide by N + for (int i = 0; i < N; i++) { + y[i] = y[i].multiply(1.0 / N); + } + + return y; + } + + // compute the inverse FFT of x[], assuming its length is a power of 2 + public static Array ifft(Array x) { + x = x.copyIfView(); + + int N = (int) x.getSize(); + Array y = Array.factory(DataType.COMPLEX, new int[]{N}); + + // take conjugate + for (int i = 0; i < N; i++) { + y.setComplex(i, x.getComplex(i).conj()); + } + + // compute forward FFT + y = fft(y); + + // take conjugate again + for (int i = 0; i < N; i++) { + y.setComplex(i, y.getComplex(i).conj()); + } + + // divide by N + for (int i = 0; i < N; i++) { + y.setComplex(i, y.getComplex(i).multiply(1.0 / N)); + } + + return y; + } + + // compute the circular convolution of x and y + public static Complex[] cConvolve(Complex[] x, Complex[] y) { + + // should probably pad x and y with 0s so that they have same length + // and are powers of 2 + if (x.length != y.length) { throw new RuntimeException("Dimensions don't agree"); } + + int N = x.length; + + // compute FFT of each sequence + Complex[] a = fft(x); + Complex[] b = fft(y); + + // point-wise multiply + Complex[] c = new Complex[N]; + for (int i = 0; i < N; i++) { + c[i] = a[i].multiply(b[i]); + } + + // compute inverse FFT + return ifft(c); + } + + + // compute the linear convolution of x and y + public static Complex[] convolve(Complex[] x, Complex[] y) { + Complex ZERO = new Complex(0, 0); + + Complex[] a = new Complex[2*x.length]; + for (int i = 0; i < x.length; i++) a[i] = x[i]; + for (int i = x.length; i < 2*x.length; i++) a[i] = ZERO; + + Complex[] b = new Complex[2*y.length]; + for (int i = 0; i < y.length; i++) b[i] = y[i]; + for (int i = y.length; i < 2*y.length; i++) b[i] = ZERO; + + return cConvolve(a, b); + } + + // display an array of Complex numbers to standard output + public static void show(Complex[] x, String title) { + System.out.println(title); + System.out.println("-------------------"); + for (int i = 0; i < x.length; i++) { + System.out.println(x[i]); + } + System.out.println(); + } + + + public static Complex[] toComplex(double[] SZ) + { + int count = SZ.length; + Complex[] C_SZ = new Complex[count]; + for (int i = 0; i < count; i++) + { + Complex d = new Complex(SZ[i],0); + C_SZ[i] = d; + } + return C_SZ; + } +} diff --git a/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FastFourierTransform.java b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FastFourierTransform.java new file mode 100644 index 00000000..14adf64f --- /dev/null +++ b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FastFourierTransform.java @@ -0,0 +1,462 @@ +package org.meteoinfo.math.transform; + +import org.apache.commons.numbers.core.ArithmeticUtils; +import org.meteoinfo.ndarray.Array; +import org.meteoinfo.ndarray.Complex; + +import java.util.Arrays; +import java.util.function.DoubleUnaryOperator; + +/** + * Implements the Fast Fourier Transform for transformation of one-dimensional + * real or complex data sets. For reference, see Applied Numerical Linear + * Algebra, ISBN 0898713897, chapter 6. + *

+ * There are several variants of the discrete Fourier transform, with various + * normalization conventions, which are specified by the parameter + * {@link Norm}. + *

+ * The current implementation of the discrete Fourier transform as a fast + * Fourier transform requires the length of the data set to be a power of 2. + * This greatly simplifies and speeds up the code. Users can pad the data with + * zeros to meet this requirement. There are other flavors of FFT, for + * reference, see S. Winograd, + * On computing the discrete Fourier transform, Mathematics of + * Computation, 32 (1978), 175 - 199. + */ +public class FastFourierTransform implements ComplexTransform { + + /** Number of array slots: 1 for "real" parts 1 for "imaginary" parts. */ + private static final int NUM_PARTS = 2; + /** + * {@code W_SUB_N_R[i]} is the real part of + * {@code exp(- 2 * i * pi / n)}: + * {@code W_SUB_N_R[i] = cos(2 * pi/ n)}, where {@code n = 2^i}. + */ + private static final double[] W_SUB_N_R = { + 0x1.0p0, -0x1.0p0, 0x1.1a62633145c07p-54, 0x1.6a09e667f3bcdp-1, + 0x1.d906bcf328d46p-1, 0x1.f6297cff75cbp-1, 0x1.fd88da3d12526p-1, 0x1.ff621e3796d7ep-1, + 0x1.ffd886084cd0dp-1, 0x1.fff62169b92dbp-1, 0x1.fffd8858e8a92p-1, 0x1.ffff621621d02p-1, + 0x1.ffffd88586ee6p-1, 0x1.fffff62161a34p-1, 0x1.fffffd8858675p-1, 0x1.ffffff621619cp-1, + 0x1.ffffffd885867p-1, 0x1.fffffff62161ap-1, 0x1.fffffffd88586p-1, 0x1.ffffffff62162p-1, + 0x1.ffffffffd8858p-1, 0x1.fffffffff6216p-1, 0x1.fffffffffd886p-1, 0x1.ffffffffff621p-1, + 0x1.ffffffffffd88p-1, 0x1.fffffffffff62p-1, 0x1.fffffffffffd9p-1, 0x1.ffffffffffff6p-1, + 0x1.ffffffffffffep-1, 0x1.fffffffffffffp-1, 0x1.0p0, 0x1.0p0, + 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0, + 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0, + 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0, + 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0, + 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0, + 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0, + 0x1.0p0, 0x1.0p0, 0x1.0p0, 0x1.0p0, + 0x1.0p0, 0x1.0p0, 0x1.0p0 }; + + /** + * {@code W_SUB_N_I[i]} is the imaginary part of + * {@code exp(- 2 * i * pi / n)}: + * {@code W_SUB_N_I[i] = -sin(2 * pi/ n)}, where {@code n = 2^i}. + */ + private static final double[] W_SUB_N_I = { + 0x1.1a62633145c07p-52, -0x1.1a62633145c07p-53, -0x1.0p0, -0x1.6a09e667f3bccp-1, + -0x1.87de2a6aea963p-2, -0x1.8f8b83c69a60ap-3, -0x1.917a6bc29b42cp-4, -0x1.91f65f10dd814p-5, + -0x1.92155f7a3667ep-6, -0x1.921d1fcdec784p-7, -0x1.921f0fe670071p-8, -0x1.921f8becca4bap-9, + -0x1.921faaee6472dp-10, -0x1.921fb2aecb36p-11, -0x1.921fb49ee4ea6p-12, -0x1.921fb51aeb57bp-13, + -0x1.921fb539ecf31p-14, -0x1.921fb541ad59ep-15, -0x1.921fb5439d73ap-16, -0x1.921fb544197ap-17, + -0x1.921fb544387bap-18, -0x1.921fb544403c1p-19, -0x1.921fb544422c2p-20, -0x1.921fb54442a83p-21, + -0x1.921fb54442c73p-22, -0x1.921fb54442cefp-23, -0x1.921fb54442d0ep-24, -0x1.921fb54442d15p-25, + -0x1.921fb54442d17p-26, -0x1.921fb54442d18p-27, -0x1.921fb54442d18p-28, -0x1.921fb54442d18p-29, + -0x1.921fb54442d18p-30, -0x1.921fb54442d18p-31, -0x1.921fb54442d18p-32, -0x1.921fb54442d18p-33, + -0x1.921fb54442d18p-34, -0x1.921fb54442d18p-35, -0x1.921fb54442d18p-36, -0x1.921fb54442d18p-37, + -0x1.921fb54442d18p-38, -0x1.921fb54442d18p-39, -0x1.921fb54442d18p-40, -0x1.921fb54442d18p-41, + -0x1.921fb54442d18p-42, -0x1.921fb54442d18p-43, -0x1.921fb54442d18p-44, -0x1.921fb54442d18p-45, + -0x1.921fb54442d18p-46, -0x1.921fb54442d18p-47, -0x1.921fb54442d18p-48, -0x1.921fb54442d18p-49, + -0x1.921fb54442d18p-50, -0x1.921fb54442d18p-51, -0x1.921fb54442d18p-52, -0x1.921fb54442d18p-53, + -0x1.921fb54442d18p-54, -0x1.921fb54442d18p-55, -0x1.921fb54442d18p-56, -0x1.921fb54442d18p-57, + -0x1.921fb54442d18p-58, -0x1.921fb54442d18p-59, -0x1.921fb54442d18p-60 }; + + /** Type of DFT. */ + protected final Norm normalization; + /** Inverse or forward. */ + protected final boolean inverse; + + /** + * @param normalization Normalization to be applied to the + * transformed data. + * @param inverse Whether to perform the inverse transform. + */ + public FastFourierTransform(final Norm normalization, + final boolean inverse) { + this.normalization = normalization; + this.inverse = inverse; + } + + /** + * @param normalization Normalization to be applied to the + * transformed data. + */ + public FastFourierTransform(final Norm normalization) { + this(normalization, false); + } + + /** + * Constructor + * + * @param inverse Whether to perform the inverse transform. + */ + public FastFourierTransform(final boolean inverse) { + this(Norm.STD, inverse); + } + + /** + * Constructor + */ + public FastFourierTransform() { + this(Norm.STD, false); + } + + /** + * Computes the standard transform of the data. + * Computation is done in place. + * Assumed layout of the input data: + *

    + *
  • {@code dataRI[0][i]}: Real part of the {@code i}-th data point,
  • + *
  • {@code dataRI[1][i]}: Imaginary part of the {@code i}-th data point.
  • + *
+ * + * @param dataRI Two-dimensional array of real and imaginary parts of the data. + * @throws IllegalArgumentException if the number of data points is not + * a power of two, if the number of rows of the specified array is not two, + * or the array is not rectangular. + */ + public void transformInPlace(final double[][] dataRI) { + if (dataRI.length != NUM_PARTS) { + throw new TransformException(TransformException.SIZE_MISMATCH, + dataRI.length, NUM_PARTS); + } + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + if (dataR.length != dataI.length) { + throw new TransformException(TransformException.SIZE_MISMATCH, + dataI.length, dataR.length); + } + final int n = dataR.length; + if (!ArithmeticUtils.isPowerOfTwo(n)) { + throw new TransformException(TransformException.NOT_POWER_OF_TWO, + Integer.valueOf(n)); + } + + if (n == 1) { + return; + } else if (n == 2) { + final double srcR0 = dataR[0]; + final double srcI0 = dataI[0]; + final double srcR1 = dataR[1]; + final double srcI1 = dataI[1]; + + // X_0 = x_0 + x_1 + dataR[0] = srcR0 + srcR1; + dataI[0] = srcI0 + srcI1; + // X_1 = x_0 - x_1 + dataR[1] = srcR0 - srcR1; + dataI[1] = srcI0 - srcI1; + + normalizeTransformedData(dataRI); + return; + } + + bitReversalShuffle2(dataR, dataI); + + // Do 4-term DFT. + if (inverse) { + for (int i0 = 0; i0 < n; i0 += 4) { + final int i1 = i0 + 1; + final int i2 = i0 + 2; + final int i3 = i0 + 3; + + final double srcR0 = dataR[i0]; + final double srcI0 = dataI[i0]; + final double srcR1 = dataR[i2]; + final double srcI1 = dataI[i2]; + final double srcR2 = dataR[i1]; + final double srcI2 = dataI[i1]; + final double srcR3 = dataR[i3]; + final double srcI3 = dataI[i3]; + + // 4-term DFT + // X_0 = x_0 + x_1 + x_2 + x_3 + dataR[i0] = srcR0 + srcR1 + srcR2 + srcR3; + dataI[i0] = srcI0 + srcI1 + srcI2 + srcI3; + // X_1 = x_0 - x_2 + j * (x_3 - x_1) + dataR[i1] = srcR0 - srcR2 + (srcI3 - srcI1); + dataI[i1] = srcI0 - srcI2 + (srcR1 - srcR3); + // X_2 = x_0 - x_1 + x_2 - x_3 + dataR[i2] = srcR0 - srcR1 + srcR2 - srcR3; + dataI[i2] = srcI0 - srcI1 + srcI2 - srcI3; + // X_3 = x_0 - x_2 + j * (x_1 - x_3) + dataR[i3] = srcR0 - srcR2 + (srcI1 - srcI3); + dataI[i3] = srcI0 - srcI2 + (srcR3 - srcR1); + } + } else { + for (int i0 = 0; i0 < n; i0 += 4) { + final int i1 = i0 + 1; + final int i2 = i0 + 2; + final int i3 = i0 + 3; + + final double srcR0 = dataR[i0]; + final double srcI0 = dataI[i0]; + final double srcR1 = dataR[i2]; + final double srcI1 = dataI[i2]; + final double srcR2 = dataR[i1]; + final double srcI2 = dataI[i1]; + final double srcR3 = dataR[i3]; + final double srcI3 = dataI[i3]; + + // 4-term DFT + // X_0 = x_0 + x_1 + x_2 + x_3 + dataR[i0] = srcR0 + srcR1 + srcR2 + srcR3; + dataI[i0] = srcI0 + srcI1 + srcI2 + srcI3; + // X_1 = x_0 - x_2 + j * (x_3 - x_1) + dataR[i1] = srcR0 - srcR2 + (srcI1 - srcI3); + dataI[i1] = srcI0 - srcI2 + (srcR3 - srcR1); + // X_2 = x_0 - x_1 + x_2 - x_3 + dataR[i2] = srcR0 - srcR1 + srcR2 - srcR3; + dataI[i2] = srcI0 - srcI1 + srcI2 - srcI3; + // X_3 = x_0 - x_2 + j * (x_1 - x_3) + dataR[i3] = srcR0 - srcR2 + (srcI3 - srcI1); + dataI[i3] = srcI0 - srcI2 + (srcR1 - srcR3); + } + } + + int lastN0 = 4; + int lastLogN0 = 2; + while (lastN0 < n) { + final int n0 = lastN0 << 1; + final int logN0 = lastLogN0 + 1; + final double wSubN0R = W_SUB_N_R[logN0]; + double wSubN0I = W_SUB_N_I[logN0]; + if (inverse) { + wSubN0I = -wSubN0I; + } + + // Combine even/odd transforms of size lastN0 into a transform of size N0 (lastN0 * 2). + for (int destEvenStartIndex = 0; destEvenStartIndex < n; destEvenStartIndex += n0) { + final int destOddStartIndex = destEvenStartIndex + lastN0; + + double wSubN0ToRR = 1; + double wSubN0ToRI = 0; + + for (int r = 0; r < lastN0; r++) { + final int destEvenStartIndexPlusR = destEvenStartIndex + r; + final int destOddStartIndexPlusR = destOddStartIndex + r; + + final double grR = dataR[destEvenStartIndexPlusR]; + final double grI = dataI[destEvenStartIndexPlusR]; + final double hrR = dataR[destOddStartIndexPlusR]; + final double hrI = dataI[destOddStartIndexPlusR]; + + final double a = wSubN0ToRR * hrR - wSubN0ToRI * hrI; + final double b = wSubN0ToRR * hrI + wSubN0ToRI * hrR; + // dest[destEvenStartIndex + r] = Gr + WsubN0ToR * Hr + dataR[destEvenStartIndexPlusR] = grR + a; + dataI[destEvenStartIndexPlusR] = grI + b; + // dest[destOddStartIndex + r] = Gr - WsubN0ToR * Hr + dataR[destOddStartIndexPlusR] = grR - a; + dataI[destOddStartIndexPlusR] = grI - b; + + // WsubN0ToR *= WsubN0R + final double nextWsubN0ToRR = wSubN0ToRR * wSubN0R - wSubN0ToRI * wSubN0I; + final double nextWsubN0ToRI = wSubN0ToRR * wSubN0I + wSubN0ToRI * wSubN0R; + wSubN0ToRR = nextWsubN0ToRR; + wSubN0ToRI = nextWsubN0ToRI; + } + } + + lastN0 = n0; + lastLogN0 = logN0; + } + + normalizeTransformedData(dataRI); + } + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the length of the data array is not a power of two. + */ + @Override + public Array apply(final double[] f) { + final double[][] dataRI = { + Arrays.copyOf(f, f.length), + new double[f.length] + }; + transformInPlace(dataRI); + return TransformUtils.createComplexArray(dataRI); + } + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the number of sample points + * {@code n} is not a power of two, if the lower bound is greater than, + * or equal to the upper bound, if the number of sample points {@code n} + * is negative + */ + @Override + public Array apply(final DoubleUnaryOperator f, + final double min, + final double max, + final int n) { + return apply(TransformUtils.sample(f, min, max, n)); + } + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the length of the data array is + * not a power of two. + */ + @Override + public Array apply(Array f) { + if (!ArithmeticUtils.isPowerOfTwo(f.getSize())) { + f = pad(f); + } + + final double[][] dataRI = TransformUtils.createRealImaginary(f); + transformInPlace(dataRI); + return TransformUtils.createComplexArray(dataRI); + } + + /** + * Applies normalization to the transformed data. + * + * @param dataRI Unscaled transformed data. + */ + private void normalizeTransformedData(final double[][] dataRI) { + if (normalization == Norm.NONE) { + return; + } + + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + final int n = dataR.length; + + switch (normalization) { + case STD: + if (inverse) { + final double scaleFactor = 1d / n; + for (int i = 0; i < n; i++) { + dataR[i] *= scaleFactor; + dataI[i] *= scaleFactor; + } + } + + break; + + case UNIT: + final double scaleFactor = 1d / Math.sqrt(n); + for (int i = 0; i < n; i++) { + dataR[i] *= scaleFactor; + dataI[i] *= scaleFactor; + } + + break; + + default: + throw new IllegalStateException(); // Should never happen. + } + } + + /** + * Performs identical index bit reversal shuffles on two arrays of + * identical size. + * Each element in the array is swapped with another element based + * on the bit-reversal of the index. + * For example, in an array with length 16, item at binary index 0011 + * (decimal 3) would be swapped with the item at binary index 1100 + * (decimal 12). + * + * @param a Array to be shuffled. + * @param b Array to be shuffled. + */ + private static void bitReversalShuffle2(double[] a, + double[] b) { + final int n = a.length; + final int halfOfN = n >> 1; + + int j = 0; + for (int i = 0; i < n; i++) { + if (i < j) { + // swap indices i & j + double temp = a[i]; + a[i] = a[j]; + a[j] = temp; + + temp = b[i]; + b[i] = b[j]; + b[j] = temp; + } + + int k = halfOfN; + while (k <= j && k > 0) { + j -= k; + k >>= 1; + } + j += k; + } + } + + /** + * Pads data so that its length is a power of 2 + * + * @param data the 1D array data + * @return the padded 1D array data + */ + private Array pad(Array data) { + int length = (int) data.getSize(); + if (ArithmeticUtils.isPowerOfTwo(length)) { + return data; + } + + int pLength = TransformUtils.nextPowerOfTwo(length); + Array padded = Array.factory(data.getDataType(), new int[]{pLength}); + for (int i = 0; i < length; i++) { + padded.setObject(i, data.getObject(i)); + } + + return padded; + } + + /** + * Normalization types. + */ + public enum Norm { + /** + * Should be passed to the constructor of {@link FastFourierTransform} + * to use the standard normalization convention. This normalization + * convention is defined as follows + *
    + *
  • forward transform: \( y_n = \sum_{k = 0}^{N - 1} x_k e^{-2 \pi i n k / N} \),
  • + *
  • inverse transform: \( x_k = \frac{1}{N} \sum_{n = 0}^{N - 1} y_n e^{2 \pi i n k / N} \),
  • + *
+ * where \( N \) is the size of the data sample. + */ + STD, + + /** + * Should be passed to the constructor of {@link FastFourierTransform} + * to use the unitary normalization convention. This normalization + * convention is defined as follows + *
    + *
  • forward transform: \( y_n = \frac{1}{\sqrt{N}} \sum_{k = 0}^{N - 1} x_k e^{-2 \pi i n k / N} \),
  • + *
  • inverse transform: \( x_k = \frac{1}{\sqrt{N}} \sum_{n = 0}^{N - 1} y_n e^{2 \pi i n k / N} \),
  • + *
+ * where \( N \) is the size of the data sample. + */ + UNIT, + + /** + * Not do normalization + */ + NONE; + } +} diff --git a/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FastFourierTransform2D.java b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FastFourierTransform2D.java new file mode 100644 index 00000000..c1ce43a2 --- /dev/null +++ b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/FastFourierTransform2D.java @@ -0,0 +1,67 @@ +package org.meteoinfo.math.transform; + +import org.meteoinfo.ndarray.Array; +import org.meteoinfo.ndarray.DataType; +import org.meteoinfo.ndarray.InvalidRangeException; +import org.meteoinfo.ndarray.Range; +import org.meteoinfo.ndarray.math.ArrayMath; +import org.meteoinfo.ndarray.math.ArrayUtil; + +import java.util.Arrays; +import java.util.List; + +public class FastFourierTransform2D extends FastFourierTransform { + + /** + * Constructor + * @param inverse Whether is inverse transform + */ + public FastFourierTransform2D(boolean inverse) { + super(inverse); + } + + @Override + public Array apply(Array f) { + f = f.copyIfView(); + + int[] shape = f.getShape(); + int nRow = shape[0]; + int nCol = shape[1]; + Array r = Array.factory(DataType.COMPLEX, shape); + + try { + FastFourierTransform fastFourierTransform = new FastFourierTransform(this.normalization, this.inverse); + + //Transform rows + Range xRange = new Range(0, nCol - 1, 1); + Range yRange; + List ranges; + for (int i = 0; i < nRow; i++) { + yRange = new Range(i, i); + ranges = Arrays.asList(yRange, xRange); + Array data = ArrayMath.section(f, ranges).copy(); + data = fastFourierTransform.apply(data); + ArrayMath.setSection(r, ranges, data); + } + + //Transform cols + yRange = new Range(0, nRow - 1, 1); + for (int i = 0; i < nCol; i++) { + xRange = new Range(i, i); + ranges = Arrays.asList(yRange, xRange); + Array data = ArrayMath.section(r, ranges).copy(); + data = fastFourierTransform.apply(data); + ArrayMath.setSection(r, ranges, data); + } + } catch (InvalidRangeException e) { + e.printStackTrace(); + } + + return r; + } + + @Override + public Array apply(double[] f) { + return null; + } +} diff --git a/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/TransformException.java b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/TransformException.java new file mode 100644 index 00000000..fd694ce6 --- /dev/null +++ b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/TransformException.java @@ -0,0 +1,37 @@ +package org.meteoinfo.math.transform; + +import java.text.MessageFormat; + +/** + * Exception class with constants for frequently used messages. + * Class is package-private (for internal use only). + */ +public class TransformException extends IllegalArgumentException { + + /** Error message for "out of range" condition. */ + public static final String FIRST_ELEMENT_NOT_ZERO = "First element ({0}) must be 0"; + /** Error message for "not strictly positive" condition. */ + public static final String NOT_STRICTLY_POSITIVE = "Number {0} is not strictly positive"; + /** Error message for "too large" condition. */ + public static final String TOO_LARGE = "Number {0} is larger than {1}"; + /** Error message for "size mismatch" condition. */ + public static final String SIZE_MISMATCH = "Size mismatch: {0} != {1}"; + /** Error message for "pow(2, n) + 1". */ + public static final String NOT_POWER_OF_TWO_PLUS_ONE = "{0} is not equal to 1 + pow(2, n), for some n"; + /** Error message for "pow(2, n)". */ + public static final String NOT_POWER_OF_TWO = "{0} is not equal to pow(2, n), for some n"; + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20210522L; + + /** + * Create an exception where the message is constructed by applying + * the {@code format()} method from {@code java.text.MessageFormat}. + * + * @param message Message format (with replaceable parameters). + * @param formatArguments Actual arguments to be displayed in the message. + */ + TransformException(String message, Object... formatArguments) { + super(MessageFormat.format(message, formatArguments)); + } +} diff --git a/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/TransformUtils.java b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/TransformUtils.java new file mode 100644 index 00000000..905f7224 --- /dev/null +++ b/meteoinfo-math/src/main/java/org/meteoinfo/math/transform/TransformUtils.java @@ -0,0 +1,237 @@ +package org.meteoinfo.math.transform; + +import java.util.function.DoubleUnaryOperator; + +import org.apache.commons.numbers.core.ArithmeticUtils; +import org.meteoinfo.ndarray.Array; +import org.meteoinfo.ndarray.Complex; +import org.meteoinfo.ndarray.DataType; + +/** + * Useful functions for the implementation of various transforms. + * Class is package-private (for internal use only). + */ +final class TransformUtils { + /** Number of array slots: 1 for "real" parts 1 for "imaginary" parts. */ + private static final int NUM_PARTS = 2; + + /** Utility class. */ + private TransformUtils() {} + + /** + * Multiply every component in the given real array by the + * given real number. The change is made in place. + * + * @param f Array to be scaled. + * @param d Scaling coefficient. + * @return a reference to the scaled array. + */ + static double[] scaleInPlace(double[] f, double d) { + for (int i = 0; i < f.length; i++) { + f[i] *= d; + } + return f; + } + + /** + * Multiply every component in the given complex array by the + * given real number. The change is made in place. + * + * @param f Array to be scaled. + * @param d Scaling coefficient. + * @return the scaled array. + */ + static Complex[] scaleInPlace(Complex[] f, double d) { + for (int i = 0; i < f.length; i++) { + f[i] = Complex.ofCartesian(d * f[i].getReal(), d * f[i].getImaginary()); + } + return f; + } + + + /** + * Builds a new two dimensional array of {@code double} filled with the real + * and imaginary parts of the specified {@link Complex} numbers. In the + * returned array {@code dataRI}, the data is laid out as follows + *
    + *
  • {@code dataRI[0][i] = dataC[i].getReal()},
  • + *
  • {@code dataRI[1][i] = dataC[i].getImaginary()}.
  • + *
+ * + * @param dataC Array of {@link Complex} data to be transformed. + * @return a two dimensional array filled with the real and imaginary parts + * of the specified complex input. + */ + static double[][] createRealImaginary(final Complex[] dataC) { + final double[][] dataRI = new double[2][dataC.length]; + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + for (int i = 0; i < dataC.length; i++) { + final Complex c = dataC[i]; + dataR[i] = c.getReal(); + dataI[i] = c.getImaginary(); + } + return dataRI; + } + + /** + * Builds a new two dimensional array of {@code double} filled with the real + * and imaginary parts of the specified {@link Complex} numbers. In the + * returned array {@code dataRI}, the data is laid out as follows + *
    + *
  • {@code dataRI[0][i] = dataC[i].getReal()},
  • + *
  • {@code dataRI[1][i] = dataC[i].getImaginary()}.
  • + *
+ * + * @param dataC Array of {@link Complex} data to be transformed. + * @return a two dimensional array filled with the real and imaginary parts + * of the specified complex input. + */ + static double[][] createRealImaginary(final Array dataC) { + dataC.copyIfView(); + + final double[][] dataRI = new double[2][(int) dataC.getSize()]; + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + for (int i = 0; i < dataC.getSize(); i++) { + final Complex c = dataC.getComplex(i); + dataR[i] = c.getReal(); + dataI[i] = c.getImaginary(); + } + return dataRI; + } + + /** + * Builds a new array of {@link Complex} from the specified two dimensional + * array of real and imaginary parts. In the returned array {@code dataC}, + * the data is laid out as follows + *
    + *
  • {@code dataC[i].getReal() = dataRI[0][i]},
  • + *
  • {@code dataC[i].getImaginary() = dataRI[1][i]}.
  • + *
+ * + * @param dataRI Array of real and imaginary parts to be transformed. + * @return a {@link Complex} array. + * @throws IllegalArgumentException if the number of rows of the specified + * array is not two, or the array is not rectangular. + */ + static Complex[] createComplex(final double[][] dataRI) { + if (dataRI.length != NUM_PARTS) { + throw new TransformException(TransformException.SIZE_MISMATCH, + dataRI.length, NUM_PARTS); + } + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + if (dataR.length != dataI.length) { + throw new TransformException(TransformException.SIZE_MISMATCH, + dataI.length, dataR.length); + } + + final int n = dataR.length; + final Complex[] c = new Complex[n]; + for (int i = 0; i < n; i++) { + c[i] = Complex.ofCartesian(dataR[i], dataI[i]); + } + return c; + } + + /** + * Builds a new array of {@link Complex} from the specified two dimensional + * array of real and imaginary parts. In the returned array {@code dataC}, + * the data is laid out as follows + *
    + *
  • {@code dataC[i].getReal() = dataRI[0][i]},
  • + *
  • {@code dataC[i].getImaginary() = dataRI[1][i]}.
  • + *
+ * + * @param dataRI Array of real and imaginary parts to be transformed. + * @return a {@link Complex} array. + * @throws IllegalArgumentException if the number of rows of the specified + * array is not two, or the array is not rectangular. + */ + static Array createComplexArray(final double[][] dataRI) { + if (dataRI.length != NUM_PARTS) { + throw new TransformException(TransformException.SIZE_MISMATCH, + dataRI.length, NUM_PARTS); + } + final double[] dataR = dataRI[0]; + final double[] dataI = dataRI[1]; + if (dataR.length != dataI.length) { + throw new TransformException(TransformException.SIZE_MISMATCH, + dataI.length, dataR.length); + } + + final int n = dataR.length; + final Array c = Array.factory(DataType.COMPLEX, new int[]{n}); + for (int i = 0; i < n; i++) { + c.setComplex(i, Complex.ofCartesian(dataR[i], dataI[i])); + } + return c; + } + + /** + * Samples the specified univariate real function on the specified interval. + *

+ * The interval is divided equally into {@code n} sections and sample points + * are taken from {@code min} to {@code max - (max - min) / n}; therefore + * {@code f} is not sampled at the upper bound {@code max}.

+ * + * @param f Function to be sampled + * @param min Lower bound of the interval (included). + * @param max Upper bound of the interval (excluded). + * @param n Number of sample points. + * @return the array of samples. + * @throws IllegalArgumentException if the lower bound {@code min} is + * greater than, or equal to the upper bound {@code max}, if the number + * of sample points {@code n} is negative. + */ + static double[] sample(DoubleUnaryOperator f, + double min, + double max, + int n) { + if (n <= 0) { + throw new TransformException(TransformException.NOT_STRICTLY_POSITIVE, + Integer.valueOf(n)); + } + if (min >= max) { + throw new TransformException(TransformException.TOO_LARGE, min, max); + } + + final double[] s = new double[n]; + final double h = (max - min) / n; + for (int i = 0; i < n; i++) { + s[i] = f.applyAsDouble(min + i * h); + } + return s; + } + + /** + * Check whether the number is a power of 2 + * @param number + * @return Whether is a power of 2 + */ + public static boolean isPowerOfTwo(int n) { + return n > 0L && (n & n - 1L) == 0L; + } + + /** + * Find next number which is a power of 2 + * @param number + * @return The next number which is a power of 2 + */ + public static int nextPowerOfTwo(int number) { + if (isPowerOfTwo(number)) { + return number; + } + + int x = number; + x = x - 1; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + + return x + 1; + } +} diff --git a/meteoinfo-ndarray/src/main/java/org/meteoinfo/ndarray/ArrayComplex.java b/meteoinfo-ndarray/src/main/java/org/meteoinfo/ndarray/ArrayComplex.java index e5a36d0e..2114a05a 100644 --- a/meteoinfo-ndarray/src/main/java/org/meteoinfo/ndarray/ArrayComplex.java +++ b/meteoinfo-ndarray/src/main/java/org/meteoinfo/ndarray/ArrayComplex.java @@ -117,7 +117,11 @@ public class ArrayComplex extends Array { if (data != null) { storage = data; } else { + int n = (int) indexCalc.getSize(); storage = new Complex[(int) indexCalc.getSize()]; + for (int i = 0; i < n; i++) { + storage[i] = Complex.ZERO; + } } } diff --git a/pom.xml b/pom.xml index 369afd86..d635583c 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ UTF-8 1.8 - 3.9.5 + 3.9.6 8 8 8