1
$\begingroup$

I have defined an integral operator hm[func_, m_, n_] that discretizes a continuous 2D function into its pixel counterpart where Dm[x_,y_,m_,n_] is the kernel of the operator. This kernel uses Mathematica's 2D UnitBox function.

Definition of the operator:

Dm[x_,y_,m_,n_] := 1/(PixelWidth)^2 UnitBox[(x - m*PixelPitch /2)/PixelWidth, 
                   (y - n*PixelPitch /2)/PixelWidth] /.{PixelWidth-> 1.2, PixelPitch -> 1.5};  

hm[func_,m_,n_] := NIntegrate[Dm[x, y, m, n] func, {x, -Infinity, Infinity},
                              {y, -Infinity, Infinity}, WorkingPrecision -> 10, 
                              PrecisionGoal -> 10, AccuracyGoal -> 10];

I define an example function that becomes the input to hm[func_,m_,n_]:

ExampleFunc[x_, y_] := Exp[-(x^2/2 + y^2/2)]; 

Here I define the pixel coordinates and generate the output, i.e. the discretized function, using hm[func_,m_,n_]:

NumberOfPixelsX = 6;    
NumberOfPixelsY = 10; 

AllPixelCords = Table[{i, j}, {i, NumberOfPixelsY/2, -NumberOfPixelsY/2, -1}, {j, NumberOfPixelsX/2, -NumberOfPixelsX/2, -1}];
FlattenedAllPixelCords = Flatten[AllPixelCords, {1, 2}];

PixelCoordinates = DeleteCases[DeleteCases[FlattenedAllPixelCords, {_, 0}, Infinity], {0, _}, Infinity];  (* Remove pixel coordinates with 0's*)

OperatorResult = hm[ExampleFunc[x, y], PixelCoordinates[[All, 1]], PixelCoordinates[[All, 2]]];

DiscretizedFunction = Partition[OperatorResult, NumberOfPixelsX];
MatrixPlot[DiscretizedFunction]

My problem is that this operation takes a lot of time to complete when I increase the number of pixels, i.e. when I use greater values for NumberOfPixelsX and NumberOfPixelsY. Is there a way to speed this up?.

$\endgroup$
9
  • $\begingroup$ What is the resolution you are aiming for? I.e. how large would you like NumberOfPixelsX/Y to be? $\endgroup$ Commented Apr 28, 2018 at 17:38
  • $\begingroup$ Another question: are you interested in this kernel specifically, for various functions, or do we also need to keep the kernel general? $\endgroup$ Commented Apr 28, 2018 at 17:54
  • $\begingroup$ I would like to use NumberOfPixelsX = 512 and NumberOfPixelsY = 1024. I am interested in this kernel because I need its value to be 1 for a specific pixel coordinate (PixelCoordinates) and 0 for all the other coordinates. You can plot Plot3D[Dm[x,y,1,1]ExampleFunc[x, y], {x,-2,2},{y,-2,2}] to see what Dm is doing and then I integrate over that area. I want to keep the kernel general because I want to apply a different functions to it. $\endgroup$
    – dykes
    Commented Apr 28, 2018 at 22:42
  • $\begingroup$ Then you should change the limits of the integration instead. The integration is what's currently taking a lot of time $\endgroup$ Commented Apr 28, 2018 at 22:45
  • 2
    $\begingroup$ Make the integration limits depend on m and n in hm[...] $\endgroup$ Commented Apr 28, 2018 at 22:56

1 Answer 1

2
$\begingroup$

Skipping Nintegrate, performing $3 \times 3$ Gauss quadrature by hand, and compiling everything into a neat, listable, and parallelized CompiledFunction:

ExampleFunc[x_, y_] := Exp[-0.5 (x^2 + y^2)];

Block[{dx, dy, gausspts, gaussweights, weights},

 {gausspts, gaussweights} = Most[NIntegrate`GaussRuleData[3, $MachinePrecision]];
 weights = Flatten[KroneckerProduct[gaussweightsx, gaussweightsy]];

 cdiscretize = With[{code = Dot[
      Flatten[
       Outer[
        ExampleFunc,
        Compile`GetElement[P, 1] + dx gausspts,
        Compile`GetElement[P, 2] + dy gausspts
        ]
       ],
      Flatten[KroneckerProduct[gaussweights, gaussweights]]
      ]},
   Compile[{{P, _Real, 1}, {dx, _Real}, {dy, _Real}},
    code,
    CompilationTarget -> "C",
    RuntimeAttributes -> {Listable},
    Parallelization -> True,
    RuntimeOptions -> "Speed"
    ]
   ];
 ]

Now lets apply this function:

{NumberOfPixelsX, NumberOfPixelsY} = {512, 1024};
{xmin, xmax} = {-1., 1.};
{ymin, ymax} = {-2., 2.};
Δx = (xmax - xmin)/NumberOfPixelsX;
Δy = (ymax - ymin)/NumberOfPixelsY;
lowerx = Most[Subdivide[xmin, xmax, NumberOfPixelsX]];
lowery = Most[Subdivide[ymin, ymax, NumberOfPixelsY]];
lowerleftcorners = Flatten[Outer[List, lowerx, lowery], 1]; // 
  AbsoluteTiming // First

discretizedFunction = Partition[
     cdiscretize[lowerleftcorners, Δx, Δy],
     NumberOfPixelsY
     ]; // AbsoluteTiming // First

ArrayPlot[discretizedFunction]

0.005842

0.02524

enter image description here

As a sanity check, we can compare to the values of the function ExampleFunc sampled on the pixel centers (this is equivalent to using $1 \times 1$ Gauss quadrature per pizel):

sampledFunction = Outer[ExampleFunc, lowerx + 0.5 Δx, lowery + 0.5 Δy];
Max[Abs[discretizedFunction - sampledFunction]]

1.27155*10^-6

Thus, the error introduced by using a $3 \times 3$ Gauss quadrature might be way below machine precision.

$\endgroup$
5
  • $\begingroup$ Your code is really fast but can I use nested NIntegrate in your code?. For example, let ExampleFunc[x_, y_] := NIntegrate[Exp[-0.5 (x^2 + y^2)] Exp[p], {p, -5, 5}];. I intend to discretize a very big function that results from a numerical integration that cannot be done using symbolic integration. $\endgroup$
    – dykes
    Commented Apr 29, 2018 at 20:08
  • $\begingroup$ In principle, you can add more dimensions to integrate over with Outer and KroneckerProduct. NIntegrate intself cannot be compiled, so the compiled approach would not work (it would be very slow due to callbacks to Mathematica's main evaluator). But every further dimension will make it more and more costly so that one would have to focus much more on specific properies of the problem at hand. $\endgroup$ Commented Apr 29, 2018 at 20:18
  • $\begingroup$ For example, Exp[-0.5 (x^2 + y^2)] Exp[p]Exp[-0.5 (x^2 + y^2)] Exp[p] can be written as product Exp[-0.5 x^2] Exp[-0.5 y^2] Exp[p] so that one can integrate it over x, y, and p independently of each other. $\endgroup$ Commented Apr 29, 2018 at 20:18
  • $\begingroup$ Thanks, I sure first have to understand how to implement Gaussian quadrature in higher dimensions. It is certainly a better approach than NIntegrage. Anyways, I tried a workaround hm[func_, m_, n_] := NIntegrate[Dm[xout, yout, m, n] func, {xout, m*0.048046875/2 - 0.048/2, m*0.048046875/2 + 0.048/2}, {yout, n*0.048046875/2 - 0.048/2, n*0.048046875/2 + 0.048/2}, WorkingPrecision -> $MachinePrecision, Method -> {Automatic, "SymbolicProcessing" -> 0}];. If I apply the ExampleFunc to this, it gives result a little faster and the results seem correct as well. What do you think about this?. $\endgroup$
    – dykes
    Commented Apr 30, 2018 at 14:14
  • $\begingroup$ "SymbolicProcessing" -> 0 is a very good idea. You can also try using Method -> {"GaussKronrodRule", "Points" -> 3}, MaxRecursion -> 0 as options for NIntegrate. $\endgroup$ Commented Apr 30, 2018 at 14:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.