The package is inspired by the high-level interface for multi-dimensional arrays provided provided by Matlab, Julia or Python's NumPy package; therefore, this tutorial presents the main features of NDArrays with parallel examples written in Matlab, Julia and Python.
If you are looking for the list of functions, you can find the documentation here: https://hakkelt.github.io/NDArrays/index.html
The source code of the package is available here: https://github.com/hakkelt/NDArrays
The NDArrays library is divided into three namespaces:
In the following, I present the main features of NDArray and ComplexNDArray interfaces through the reference implementation. This tutorial assumes that the reader is familiar with anonymous functions and streams in Java.
>java
%maven io.github.hakkelt:ndarrays:2.2.0
import io.github.hakkelt.ndarrays.NDArray;
import io.github.hakkelt.ndarrays.basic.BasicDoubleNDArray;
import io.github.hakkelt.ndarrays.basic.BasicIntegerNDArray;
import java.util.stream.IntStream;
>python
import numpy as np
def print_matrix(matrix):
if matrix.ndim == 2:
print(matrix)
else:
print(matrix.transpose((2, 0, 1)))
>java
NDArray<Double> zeroArray = new BasicDoubleNDArray(4, 5); // Basic NDArray initialized with zeros by default
System.out.println("Array of zeros: " + zeroArray.contentToString());
NDArray<Double> twosArray = new BasicDoubleNDArray(4, 5).fill(2);
System.out.println("Array of twos: " + twosArray.contentToString())
Array of zeros: basic NDArray<Double>(4 × 5) 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 Array of twos: basic NDArray<Double>(4 × 5) 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00 2,00000e+00
>matlab
disp("Array of zeros:")
zeroArray = zeros(4, 5)
disp("Array of twos:")
twosArray = zeroArray; % copy matrix
twosArray(:) = 2; % assign value
twosArray % print
Array of zeros: zeroArray = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Array of twos: twosArray = 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
>python
print("Array of zeros:")
print_matrix(np.zeros((4, 5)))
print("\nArray of twos:")
twosArray = np.ndarray((4, 5))
twosArray.fill(2)
print_matrix(twosArray)
Array of zeros: [[0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.]] Array of twos: [[2. 2. 2. 2. 2.] [2. 2. 2. 2. 2.] [2. 2. 2. 2. 2.] [2. 2. 2. 2. 2.]]
>julia-1.8
print("Array of zeros:"); flush(stdout)
zerosArray = zeros(4, 5)
display(zerosArray)
print("\nArray of twos:"); flush(stdout)
twosArray = fill(2, size(zerosArray))
display(twosArray)
Array of zeros:
4×5 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Array of twos:
4×5 Matrix{Int64}: 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
Linear index: Index of elements imagining that the array is reshaped into a vector.
Column/row-major indexing:
Note that NDArray, Matlab and Julia uses column-major ordering, while NumPy uses row-major ordering.
Also note that while Matlab and Julia start indexing from 1, NDArray and NumPy uses zero-based indexing.
>java
new BasicDoubleNDArray(4, 5) // create array
.fillUsingLinearIndices(i -> (double) i) // initialize
.contentToString() // print content
basic NDArray<Double>(4 × 5) 0,00000e+00 4,00000e+00 8,00000e+00 1,20000e+01 1,60000e+01 1,00000e+00 5,00000e+00 9,00000e+00 1,30000e+01 1,70000e+01 2,00000e+00 6,00000e+00 1,00000e+01 1,40000e+01 1,80000e+01 3,00000e+00 7,00000e+00 1,10000e+01 1,50000e+01 1,90000e+01
>matlab
array = zeros(4, 5); % create array
array(:) = 0:19 % reshape to vector, assign values, and print content
array = 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19
>python
array = np.ndarray((4, 5)) # create array
linearIndex = 0
for j in range(5):
for i in range(4):
array[i,j] = linearIndex; # initialize elements separately
linearIndex += 1
print_matrix(array) # print content
[[ 0. 4. 8. 12. 16.] [ 1. 5. 9. 13. 17.] [ 2. 6. 10. 14. 18.] [ 3. 7. 11. 15. 19.]]
>julia-1.8
array = zeros(4, 5) # create array
array[:] .= 0:19 # reshape to vector and assign values
array # print content
4×5 Matrix{Float64}: 0.0 4.0 8.0 12.0 16.0 1.0 5.0 9.0 13.0 17.0 2.0 6.0 10.0 14.0 18.0 3.0 7.0 11.0 15.0 19.0
Cartesian index: fancy name for "usual" indexing with separate coordinates for each dimensions.
>java
new BasicDoubleNDArray(4, 5)
.fillUsingCartesianIndices(idx -> (double) (idx[0] * idx[0] + idx[1] * idx[1]))
.contentToString()
basic NDArray<Double>(4 × 5) 0,00000e+00 1,00000e+00 4,00000e+00 9,00000e+00 1,60000e+01 1,00000e+00 2,00000e+00 5,00000e+00 1,00000e+01 1,70000e+01 4,00000e+00 5,00000e+00 8,00000e+00 1,30000e+01 2,00000e+01 9,00000e+00 1,00000e+01 1,30000e+01 1,80000e+01 2,50000e+01
>matlab
array = zeros(4, 5);
for i = 0:3
for j = 0:4
array(i + 1, j + 1) = i*i + j*j;
end
end
array
array = 0 1 4 9 16 1 2 5 10 17 4 5 8 13 20 9 10 13 18 25
>python
array = np.ndarray((4, 5))
for i in range(4):
for j in range(5):
array[i, j] = i*i + j*j
print_matrix(array)
[[ 0. 1. 4. 9. 16.] [ 1. 2. 5. 10. 17.] [ 4. 5. 8. 13. 20.] [ 9. 10. 13. 18. 25.]]
>julia-1.8
array = [i*i + j*j for i in 0:3, j in 0:4]
4×5 Matrix{Int64}: 0 1 4 9 16 1 2 5 10 17 4 5 8 13 20 9 10 13 18 25
Note that for technical reason, this initialization is done by factory methods (.of()
static methods, so new
keyword shall not be used)!
>java
int[][] data = new int[4][5];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 5; j++)
data[i][j] = i*i + j*j;
BasicDoubleNDArray.of(data).contentToString()
basic NDArray<Double>(4 × 5) 0,00000e+00 1,00000e+00 4,00000e+00 9,00000e+00 1,60000e+01 1,00000e+00 2,00000e+00 5,00000e+00 1,00000e+01 1,70000e+01 4,00000e+00 5,00000e+00 8,00000e+00 1,30000e+01 2,00000e+01 9,00000e+00 1,00000e+01 1,30000e+01 1,80000e+01 2,50000e+01
Usually, copy constructor is useful to convert between different types of NDArray.
>java
NDArray<Double> array1 = new BasicDoubleNDArray(4, 5).fill(3);
NDArray<Integer> array2 = new BasicIntegerNDArray(array1); // convert double array to int array
array2.contentToString()
basic NDArray<Integer>(4 × 5) 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
Sometimes, NDArray collectors might be a little tricky. For instance, in the following code BasicDoubleNDArray.getCollector(4, 5)
is capable of receiving only Double
values, so first we need to convert int
to double
(mapToDouble
, and (double)
casting), and the double
to Double
(hence we need boxed()
). Usually there are more convenient alternatives, but it can be handy when we want to both transform the data and change its type (like squaring integers (transformation) and casting them to double).
>java
NDArray<Double> array = IntStream.range(0, 20)
.mapToDouble(i -> (double) (i * i))
.boxed()
.collect(BasicDoubleNDArray.getCollector(4, 5));
array.contentToString()
basic NDArray<Double>(4 × 5) 0,00000e+00 1,60000e+01 6,40000e+01 1,44000e+02 2,56000e+02 1,00000e+00 2,50000e+01 8,10000e+01 1,69000e+02 2,89000e+02 4,00000e+00 3,60000e+01 1,00000e+02 1,96000e+02 3,24000e+02 9,00000e+00 4,90000e+01 1,21000e+02 2,25000e+02 3,61000e+02
>java
NDArray<Double> array = new BasicDoubleNDArray(4, 5);
array.set(5, 0, 1); // Cartesian indexing starting from 0
array.set(-1, 8); // linear indexing starting from 0
System.out.println(array.contentToString());
System.out.format("Element at [0,1]: %g%n", array.get(0, 1));
System.out.format("Element at linear index 8: %g", array.get(8));
basic NDArray<Double>(4 × 5) 0,00000e+00 5,00000e+00 -1,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 Element at [0,1]: 5,00000 Element at linear index 8: -1,00000
java.io.PrintStream@d3f4d30
>matlab
array = zeros(4, 5);
array(1, 2) = 5; % Cartesian indexing starting from 1
array(9) = -1; % linear indexing starting from 1
array
fprintf("Element at [1,2]: %d", array(1,2))
fprintf("Element at linear index 9: %d", array(9))
array = 0 5 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Element at [1,2]: 5Element at linear index 9: -1
>python
array = np.zeros((4, 5))
array[0, 1] = 5 # Cartesian indexing starting from 0
array.flat[2] = -1 # Linear indexing starting from 0 (row-major indexing!)
print_matrix(array)
print(f'Element at [0,1]: {array[0, 1]}\nElement at linear index 2: {array.flat[2]}')
[[ 0. 5. -1. 0. 0.] [ 0. 0. 0. 0. 0.] [ 0. 0. 0. 0. 0.] [ 0. 0. 0. 0. 0.]] Element at [0,1]: 5.0 Element at linear index 2: -1.0
>julia-1.8
array = zeros(4, 5)
array[1,2] = 5 # Cartesian indexing starting from 1
array[9] = -1 # Linear indexing starting from 1
display(array)
println("Element at [1,2]: $(array[1,2])\nElement at linear index #9: $(array[9])")
4×5 Matrix{Float64}: 0.0 5.0 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
Element at [1,2]: 5.0 Element at linear index #9: -1.0
>java
NDArray<Double> array = new BasicDoubleNDArray(4, 5, 3);
System.out.println("Is the type of array elements double? " + (Double.class == array.dtype()));
System.out.println("Name of the type of array elements: " + array.dataTypeAsString());
System.out.println("Number of dimensions: " + array.ndim());
int[] shape = array.shape();
System.out.format("Shape of array: %d × %d × %d%n", shape[0], shape[1], shape[2]);
System.out.format("Shape of array: %d × %d × %d%n", array.shape(0), array.shape(1), array.shape(2));
System.out.println("Number of elements in the array: " + array.length());
System.out.println("String representation of array: " + array.toString());
Is the type of array elements double? true Name of the type of array elements: Double Number of dimensions: 3 Shape of array: 4 × 5 × 3 Shape of array: 4 × 5 × 3 Number of elements in the array: 60 String representation of array: basic NDArray<Double>(4 × 5 × 3)
>matlab
array = zeros(4, 5, 3);
fprintf("Is the type of array elements double? ");
if isa(array, "double")
fprintf("true\n")
else
fprintf("false\n")
end
fprintf("Name of the type of array elements: %s\n", class(array))
fprintf("Number of dimensions: %d\n", ndims(array))
fprintf("Shape of array: %s\n", mat2str(size(array)))
fprintf("Number of elements in the array: %d\n", numel(array))
Is the type of array elements double? true Name of the type of array elements: double Number of dimensions: 3 Shape of array: [4 5 3] Number of elements in the array: 60
>python
array = np.zeros((4, 5, 3))
print("Is the type of array elements double?", np.float64 == array.dtype)
print("Name of the type of array elements:", array.dtype)
print("Number of dimensions:", array.ndim)
print("Shape of array:", " × ".join(map(str, array.shape)))
print("Number of elements in the array:", len(array.flat))
Is the type of array elements double? True Name of the type of array elements: float64 Number of dimensions: 3 Shape of array: 4 × 5 × 3 Number of elements in the array: 60
>julia-1.8
array = zeros(4, 5, 3)
println("Is the type of array elements double? ", Float64 == eltype(array))
println("Name of the type of array elements: ", eltype(array))
println("Number of dimensions: ", ndims(array))
println("Shape of array: ", join(size(array), " × "))
println("Number of elements in the array: ", length(array))
Is the type of array elements double? true Name of the type of array elements: Float64 Number of dimensions: 3 Shape of array: 4 × 5 × 3 Number of elements in the array: 60
>java
NDArray<Double> array = new BasicDoubleNDArray(3,3).fillUsingLinearIndices(i -> (double) i);
System.out.println("For-each loop:");
for (var elem : array)
System.out.println(elem);
For-each loop: 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0
>java
System.out.println("\nSequential stream:");
array.stream().forEach(System.out::println);
System.out.println("\nParallel stream:");
array.parallelStream().forEach(System.out::println); // parallelStream() == stream().parallel()
Sequential stream: 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 Parallel stream: 5.0 4.0 3.0 1.0 8.0 6.0 2.0 7.0 0.0
>java
// Note that these are all parallel operations!
System.out.println("\nParallel forEach:");
array.forEach(System.out::println);
System.out.println("\nParallel forEachWithCartesianIndices:");
array.forEachWithCartesianIndices((value, idx) -> System.out.format("[%d,%d]: %.1f%n", idx[0], idx[1], value));
System.out.println("\nParallel forEachWithLinearIndices:");
array.forEachWithLinearIndices((value, idx) -> System.out.format("#%d: %.1f%n", idx, value));
Parallel forEach: 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 Parallel forEachWithCartesianIndices: [0,0]: 0,0 [1,0]: 1,0 [2,0]: 2,0 [0,1]: 3,0 [1,1]: 4,0 [2,1]: 5,0 [0,2]: 6,0 [1,2]: 7,0 [2,2]: 8,0 Parallel forEachWithLinearIndices: #0: 0,0 #1: 1,0 #2: 2,0 #3: 3,0 #4: 4,0 #5: 5,0 #6: 6,0 #7: 7,0 #8: 8,0
>matlab
array = reshape(1:9, 3, 3);
disp("Sequential loop")
for i = 1:3
for j = 1:3
disp(num2str(array(i,j)))
end
end
fprintf("\nParallel loop:\n")
parfor idx = 1:9
i = mod(idx, 3) + 1;
j = ceil(idx / 3);
fprintf("[%d,%d]: %d\n", i, j, array(i,j))
end
Sequential loop 1 4 7 2 5 8 3 6 9 Parallel loop: [1,3]: 7 [3,3]: 9 [2,3]: 8 [1,2]: 4 [3,2]: 6 [2,2]: 5 [1,1]: 1 [3,1]: 3 [2,1]: 2
>python
array = np.arange(1, 10).reshape((3,3))
for elem in array.flat:
print(elem)
# As far as I know one can parallelize python/numpy operations only with the help of external libraries
1 2 3 4 5 6 7 8 9
>julia-1.8
array = collect(reshape(1:9, 3, 3))
println("Sequential for-each:")
for elem in array
println(elem)
end
println("\nParallel for-each:")
Threads.@threads for idx in CartesianIndices(array)
println("[$(idx[1]),$(idx[2])]: $(array[idx])")
end
Sequential for-each: 1 2 3 4 5 6 7 8 9 Parallel for-each: [1,1]: 1 [3,3]: 9 [2,1]: 2 [3,1]: 3 [1,2]: 4 [2,2]: 5 [2,3]: 8 [3,2]: 6 [1,3]: 7
Mapping creates a new array and leaves the original intact, while transformation modifies the original array.
Keep in mind that both mapping and transformation runs in parallel, so the anonymous functions given as parameters MUST NOT change variables outside of the function body. In exchange, these operations will be much faster then conventional solutions (for loop + fetch + transform + set) both because they are parallelized and because no boundary checks are needed when fetching/setting elements.
>java
NDArray<Integer> array = new BasicIntegerNDArray(3, 4).fillUsingLinearIndices(i -> i);
// The conventional, non-parallel solution looks like this:
NDArray<Integer> conventionallySquared = new BasicIntegerNDArray(3, 4);
for (int i = 0; i < array.length(); i++) {
int elem = array.get(i); // fetch
int transformed = elem * elem; // transform
conventionallySquared.set(transformed, i); // set
}
NDArray<Integer> squared = array.map(elem -> elem * elem);
System.out.println("New array created by mapping: " + squared.contentToString());
NDArray<Integer> squared2 = array.mapWithLinearIndices((elem, i) -> i % 2 == 0 ? elem : elem * elem);
System.out.println("Another array created by mapping: " + squared2.contentToString());
array.apply(elem -> elem * elem * elem);
System.out.println("Original array after transformation: " + array.contentToString());
array.applyWithCartesianIndices((elem, idx) -> idx[1] % 2 == 0 ? elem : 0);
System.out.println("Original array after the second transformation: " + array.contentToString());
New array created by mapping: basic NDArray<Integer>(3 × 4) 0 9 36 81 1 16 49 100 4 25 64 121 Another array created by mapping: basic NDArray<Integer>(3 × 4) 0 9 6 81 1 4 49 10 2 25 8 121 Original array after transformation: basic NDArray<Integer>(3 × 4) 0 27 216 729 1 64 343 1000 8 125 512 1331 Original array after the second transformation: basic NDArray<Integer>(3 × 4) 0 0 216 0 1 0 343 0 8 0 512 0
>matlab
array = reshape(0:11, 3, 4);
squared = array .^ 2;
disp("New array created by mapping:")
disp(squared)
squared2 = array; % create a copy
squared2(2:2:end) = array(2:2:end) .^ 2;
disp("Another array created by mapping:")
disp(squared2)
array = array .^ 3;
disp("Original array after transformation:")
disp(array)
array(:,2:2:end) = 0;
disp("Original array after the second transformation:")
disp(array)
New array created by mapping: 0 9 36 81 1 16 49 100 4 25 64 121 Another array created by mapping: 0 9 6 81 1 4 49 10 2 25 8 121 Original array after transformation: 0 27 216 729 1 64 343 1000 8 125 512 1331 Original array after the second transformation: 0 0 216 0 1 0 343 0 8 0 512 0
>python
array = np.arange(0, 12).reshape((4,3)).T
print("New array created by mapping:")
squared = array ** 2
print_matrix(squared)
print("\nAnother array created by mapping:")
squared2 = array.copy(); # create a copy
squared2.flat[1::2] = array.flat[1::2] ** 2
print_matrix(squared2)
print("\nOriginal array after transformation:")
array **= 3
print_matrix(array)
array.T[:,1::2] = 0
print("\nOriginal array after the second transformation:")
print_matrix(array)
New array created by mapping: [[ 0 9 36 81] [ 1 16 49 100] [ 4 25 64 121]] Another array created by mapping: [[ 0 9 6 81] [ 1 16 7 100] [ 2 25 8 121]] Original array after transformation: [[ 0 27 216 729] [ 1 64 343 1000] [ 8 125 512 1331]] Original array after the second transformation: [[ 0 27 216 729] [ 0 0 0 0] [ 8 125 512 1331]]
>julia-1.8
array = collect(reshape(0:11, 3, 4));
squared = array .^ 2;
print("New array created by mapping:"); flush(stdout)
display(squared)
squared2 = [i % 2 == 1 ? elem : elem * elem for (i, elem) in enumerate(array)]
print("\nAnother array created by mapping:"); flush(stdout)
display(squared2)
array .^= 3;
print("\nOriginal array after transformation:"); flush(stdout)
display(array)
array[:,2:2:end] .= 0;
print("\nOriginal array after the second transformation:"); flush(stdout)
display(array)
New array created by mapping:
3×4 Matrix{Int64}: 0 9 36 81 1 16 49 100 4 25 64 121
Another array created by mapping:
3×4 Matrix{Int64}: 0 9 6 81 1 4 49 10 2 25 8 121
Original array after transformation:
3×4 Matrix{Int64}: 0 27 216 729 1 64 343 1000 8 125 512 1331
Original array after the second transformation:
3×4 Matrix{Int64}: 0 0 216 0 1 0 343 0 8 0 512 0
Views are basically wrappers around the original data structure. They are lazy: they don't perform any operations (i.e., don't copy data), instead they reference the specified region its parent array. Therefore, all modifications in the parent array are reflected in the view, and vice versa.
They reference their parent array giving read-write access to a specific multi-dimensional slice of the parent array.
>java
import io.github.hakkelt.ndarrays.Range;
NDArray<Integer> array = new BasicIntegerNDArray(4, 5, 2).fillUsingLinearIndices(i -> i);
System.out.println("Original array: " + array.contentToString());
// Select rows #2 and #3, every second column, and only the first matrix along the third axis
// Constructor of Range class takes the start index, and the exclusive end index of the range.
// Optionally, it can also take the step size as the second parameter (see: new Range(0, 2, 5))
NDArray<Integer> slice = array.slice(new Range(2,4), new Range(0, 2, 5), 0);
// Alternatively, for fixed ranges, we can use string literals:
slice = array.slice("2:4", "0:2:5", 0);
System.out.println("Slice: " + slice.contentToString());
// Modifications to the slice are reflected in the parent array!
slice.fill(-1);
System.out.println("Original array after modifying the slice: " + array.contentToString());
Original array: basic NDArray<Integer>(4 × 5 × 2) [:, :, 0] = 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 [:, :, 1] = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39 Slice: basic NDArrayView<Integer>(2 × 3) 2 10 18 3 11 19 Original array after modifying the slice: basic NDArray<Integer>(4 × 5 × 2) [:, :, 0] = 0 4 8 12 16 1 5 9 13 17 -1 6 -1 14 -1 -1 7 -1 15 -1 [:, :, 1] = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39
>matlab
disp("Original array:")
array = reshape(0:39, 4, 5, 2)
slice = array(3:4, 1:2:5, 1)
% Let's modify the slice!
array(3:4, 1:2:5, 1) = -1;
fprintf("\n\nOriginal array after modifying the slice:\n")
array
Original array: array(:,:,1) = 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 array(:,:,2) = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39 slice = 2 10 18 3 11 19 Original array after modifying the slice: array(:,:,1) = 0 4 8 12 16 1 5 9 13 17 -1 6 -1 14 -1 -1 7 -1 15 -1 array(:,:,2) = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39
>python
print("Original array:")
array = np.arange(0, 40).reshape((2,5,4)).transpose((2,1,0)) # We'll get 4 × 5 × 2 array, and we need this transposition because of the row-major ordering
print_matrix(array)
# Let's modify the slice!
print("\nSlice:")
slice = array[2:4, ::2, 0]
print_matrix(slice)
slice.flat = -1;
print("\nOriginal array after modifying the slice:")
print_matrix(array)
Original array: [[[ 0 4 8 12 16] [ 1 5 9 13 17] [ 2 6 10 14 18] [ 3 7 11 15 19]] [[20 24 28 32 36] [21 25 29 33 37] [22 26 30 34 38] [23 27 31 35 39]]] Slice: [[ 2 10 18] [ 3 11 19]] Original array after modifying the slice: [[[ 0 4 8 12 16] [ 1 5 9 13 17] [-1 6 -1 14 -1] [-1 7 -1 15 -1]] [[20 24 28 32 36] [21 25 29 33 37] [22 26 30 34 38] [23 27 31 35 39]]]
>julia-1.8
array = collect(reshape(0:39, 4, 5, 2))
println("Original array:"); flush(stdout)
display(array)
slice = @view array[3:4, 1:2:5, 1]
println("\nSlice:"); flush(stdout)
display(slice)
# Let's modify the slice!
slice .= -1;
# Alternative solution:
array[3:4, 1:2:5, 1] .= -1
println("\nOriginal array after modifying the slice:"); flush(stdout)
display(array)
Original array:
4×5×2 Array{Int64, 3}: [:, :, 1] = 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 [:, :, 2] = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39
Slice:
2×3 view(::Array{Int64, 3}, 3:4, 1:2:5, 1) with eltype Int64: 2 10 18 3 11 19
Original array after modifying the slice:
4×5×2 Array{Int64, 3}: [:, :, 1] = 0 4 8 12 16 1 5 9 13 17 -1 6 -1 14 -1 -1 7 -1 15 -1 [:, :, 2] = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39
To avoid the modification of the original array, we need to create explicitly a copy of the slice.
>java
import io.github.hakkelt.ndarrays.Range;
NDArray<Integer> array = new BasicIntegerNDArray(4, 5).fillUsingLinearIndices(i -> i);
System.out.println("Original array before modifying the slice: " + array.contentToString());
NDArray<Integer> slice = array.slice(new Range(2,4), new Range(0, 2, 5)).copy(); // here we make a copy of the slice!
slice.fill(-1);
System.out.println("Modified slice: " + slice.contentToString());
System.out.println("Original array after modifying the slice: " + array.contentToString());
Original array before modifying the slice: basic NDArray<Integer>(4 × 5) 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 Modified slice: basic NDArray<Integer>(2 × 3) -1 -1 -1 -1 -1 -1 Original array after modifying the slice: basic NDArray<Integer>(4 × 5) 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19
In Matlab, slicing creates a copy automatically (except when it appears on the left hand side of an assignment).
>matlab
disp("Original array before modifying the slice:")
array = reshape(0:39, 4, 5, 2)
slice = array(3:4, 1:2:5, 1); % this line creates a copy of the referenced sub-matrix!
fprintf("\n\nModified slice:\n")
slice(:) = -1 % Let's modify the slice!
fprintf("\n\nOriginal array after modifying the slice:\n")
array
Original array before modifying the slice: array(:,:,1) = 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 array(:,:,2) = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39 Modified slice: slice = -1 -1 -1 -1 -1 -1 Original array after modifying the slice: array(:,:,1) = 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 array(:,:,2) = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39
With NumPy, we also need to create a copy explicitly.
>python
print("Original array before modifying the slice:")
array = np.arange(0, 40).reshape((2,5,4)).transpose((2,1,0)) # transposition because of the row-major ordering, again...
print_matrix(array)
# Let's modify the slice!
print("\nModified slice:")
slice = array[2:4, ::2, 0].copy()
slice.flat = -1;
print_matrix(slice)
print("\nOriginal array after modifying the slice:")
print_matrix(array)
Original array before modifying the slice: [[[ 0 4 8 12 16] [ 1 5 9 13 17] [ 2 6 10 14 18] [ 3 7 11 15 19]] [[20 24 28 32 36] [21 25 29 33 37] [22 26 30 34 38] [23 27 31 35 39]]] Modified slice: [[-1 -1 -1] [-1 -1 -1]] Original array after modifying the slice: [[[ 0 4 8 12 16] [ 1 5 9 13 17] [ 2 6 10 14 18] [ 3 7 11 15 19]] [[20 24 28 32 36] [21 25 29 33 37] [22 26 30 34 38] [23 27 31 35 39]]]
Like Matlab, Julia creates a copy by default when slicing is performed (except when it appears on the left hand side of an assignment):
>julia-1.8
array = collect(reshape(0:39, 4, 5, 2))
println("Original array before modifying the slice:"); flush(stdout)
display(array)
slice = array[3:4, 1:2:5, 1] # here we create a copy
slice .= -1; # Let's modify the slice!
println("\nModified slice:"); flush(stdout)
display(slice)
println("\nOriginal array after modifying the slice:"); flush(stdout)
display(array)
Original array before modifying the slice:
4×5×2 Array{Int64, 3}: [:, :, 1] = 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 [:, :, 2] = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39
Modified slice:
2×3 Matrix{Int64}: -1 -1 -1 -1 -1 -1
Original array after modifying the slice:
4×5×2 Array{Int64, 3}: [:, :, 1] = 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 [:, :, 2] = 20 24 28 32 36 21 25 29 33 37 22 26 30 34 38 23 27 31 35 39
Ranges starting from beginning, going until end, or selecting entire range.
>java
NDArray<Integer> array = new BasicIntegerNDArray(4, 5, 3);
// Omitting the start index implies a range starting from 0
// Omitting the end index implies that the range goes until the end of the specific dimension
// Omitting both start and end selects maximal range
NDArray<Integer> slice = array.slice(":2", "3:", ":");
slice = array.slice(":2", "3:", Range.ALL); // alternative solution for selecting entire range
System.out.format("Shape of slice that selects the first two rows, all columns starting from index 3, and all the slices: %d × %d × %d",
slice.shape(0), slice.shape(1), slice.shape(2));
Shape of slice that selects the first two rows, all columns starting from index 3, and all the slices: 2 × 2 × 3
java.io.PrintStream@d3f4d30
>matlab
array = zeros(4, 5, 3);
slice = array(1:2,4:end,:);
fprintf("Shape of slice that selects the first two rows, all columns starting from index 4, and all the slices: %d × %d × %d", size(slice, 1), size(slice, 2), size(slice, 3))
Shape of slice that selects the first two rows, all columns starting from index 4, and all the slices: 2 × 2 × 3
>python
array = np.zeros((4, 5, 3))
slice = array[0:2,3:,:]
print(f"Shape of slice that selects the first two rows, all columns starting from index 3, and all the slices:",
" × ".join(map(str, slice.shape)))
Shape of slice that selects the first two rows, all columns starting from index 3, and all the slices: 2 × 2 × 3
>julia-1.8
array = zeros(4, 5, 3)
slice = array[1:2,4:end,:]
print("Shape of slice that selects the first two rows, all columns starting from index 3, and all the slices: ")
print(join(size(slice), " × "))
Shape of slice that selects the first two rows, all columns starting from index 3, and all the slices: 2 × 2 × 3
>java
NDArray<Integer> array = new BasicIntegerNDArray(4, 5).fillUsingLinearIndices(i -> i);
System.out.println("Original array: " + array.contentToString());
NDArray<Integer> slice = array.slice("-2:", new Range(4, -2, -1));
System.out.println("Slice (last two rows and every second columns in reverse orders): " + slice.contentToString());
Original array: basic NDArray<Integer>(4 × 5) 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 Slice (last two rows and every second columns in reverse orders): basic NDArrayView<Integer>(2 × 3) 18 10 2 19 11 3
>matlab
disp("Original array:")
array = reshape(0:19, 4, 5);
disp(array)
disp("Slice (last two rows and every second columns in reverse orders):")
disp(array(end-1:end,5:-2:1))
Original array: 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 Slice (last two rows and every second columns in reverse orders): 18 10 2 19 11 3
>python
print("Original array:")
array = np.arange(0, 20).reshape(5, 4).T
print_matrix(array)
print("\nSlice (last two rows and every second columns in reverse orders):")
print_matrix(array[-2:,4::-2])
Original array: [[ 0 4 8 12 16] [ 1 5 9 13 17] [ 2 6 10 14 18] [ 3 7 11 15 19]] Slice (last two rows and every second columns in reverse orders): [[18 10 2] [19 11 3]]
>julia-1.8
print("Original array:"); flush(stdout)
array = collect(reshape(0:19, 4, 5))
display(array)
print("\nSlice (last two rows and every second columns in reverse orders):"); flush(stdout)
display(array[end-1:end,end:-2:1])
Original array:
4×5 Matrix{Int64}: 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19
Slice (last two rows and every second columns in reverse orders):
2×3 Matrix{Int64}: 18 10 2 19 11 3
>java
NDArray<Integer> array = new BasicIntegerNDArray(4, 5).fillUsingLinearIndices(i -> i);
System.out.println("Original array: " + array.contentToString());
NDArray<Integer> checkerMask = new BasicIntegerNDArray(4, 5).fillUsingCartesianIndices(idx -> (idx[0] + idx[1]) % 2);
System.out.println("Selection mask: " + checkerMask.contentToString());
NDArray<Integer> selected = array.mask(checkerMask);
System.out.println("Selected values: " + selected.contentToString());
array.mask(value -> value % 3 == 0).fill(0);
System.out.println("\nOriginal array after zeroing out number divisible by 3: " + array.contentToString());
array.maskWithCartesianIndices((value, idx) -> idx[1] < 3 && value % 2 == 0).apply(value -> value * value);
System.out.println("Original array after squaring even values in the first three columns: " + array.contentToString());
Original array: basic NDArray<Integer>(4 × 5) 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 Selection mask: basic NDArray<Integer>(4 × 5) 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 Selected values: basic NDArrayView<Integer>(10) 1 3 4 6 9 11 12 14 17 19 Original array after zeroing out number divisible by 3: basic NDArray<Integer>(4 × 5) 0 4 8 0 16 1 5 0 13 17 2 0 10 14 0 0 7 11 0 19 Original array after squaring even values in the first three columns: basic NDArray<Integer>(4 × 5) 0 16 64 0 16 1 5 0 13 17 4 0 100 14 0 0 7 11 0 19
>matlab
disp("Original array:")
array = reshape(0:19, 4, 5);
disp(array)
disp("Selection mask:")
checkerMask = ones(4, 5);
checkerMask(2:2:end,2:2:end) = 0;
checkerMask(1:2:end,1:2:end) = 0;
checkerMask = checkerMask == 1;
disp(checkerMask)
disp("Selected values:")
disp(array(checkerMask)')
disp("Original array after zeroing out number divisible by 3:")
array(mod(array, 3) == 0) = 0;
disp(array)
disp("Original array after squaring even values in the first three columns:")
mask_first_three_column = zeros(size(array));
mask_first_three_column(:,1:3) = 1;
mask_even_elements = mod(array, 2) == 0;
array(mask_first_three_column & mask_even_elements) = array(mask_first_three_column & mask_even_elements) .^ 2;
disp(array)
Original array: 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 Selection mask: 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 Selected values: 1 3 4 6 9 11 12 14 17 19 Original array after zeroing out number divisible by 3: 0 4 8 0 16 1 5 0 13 17 2 0 10 14 0 0 7 11 0 19 Original array after squaring even values in the first three columns: 0 16 64 0 16 1 5 0 13 17 4 0 100 14 0 0 7 11 0 19
>python
print("Original array:")
array = np.arange(0, 20).reshape(5, 4).T;
print_matrix(array)
print("\nSelection mask:")
checkerMask = np.asarray([[(i + j) % 2 for i in range(0, 5)] for j in range(0, 4)]);
print_matrix(checkerMask)
print("\nSelected values:")
print(array.T[checkerMask.T == 1])
print("\nOriginal array after zeroing out number divisible by 3:")
array[array % 3 == 0] = 0;
print_matrix(array)
print("\nOriginal array after squaring even values in the first three columns:")
first_three_column = array[:,:3]
first_three_column[first_three_column % 2 == 0] **= 2
print_matrix(array)
Original array: [[ 0 4 8 12 16] [ 1 5 9 13 17] [ 2 6 10 14 18] [ 3 7 11 15 19]] Selection mask: [[0 1 0 1 0] [1 0 1 0 1] [0 1 0 1 0] [1 0 1 0 1]] Selected values: [ 1 3 4 6 9 11 12 14 17 19] Original array after zeroing out number divisible by 3: [[ 0 4 8 0 16] [ 1 5 0 13 17] [ 2 0 10 14 0] [ 0 7 11 0 19]] Original array after squaring even values in the first three columns: [[ 0 16 64 0 16] [ 1 5 0 13 17] [ 4 0 100 14 0] [ 0 7 11 0 19]]
>julia-1.8
print("Original array:"); flush(stdout)
array = collect(reshape(0:19, 4, 5))
display(array)
print("\nSelection mask:"); flush(stdout)
checkerMask = [(i + j) % 2 for i in 1:4, j in 1:5];
display(checkerMask)
print("\nSelected values:"); flush(stdout)
display(array[checkerMask .== 1]')
print("\nOriginal array after zeroing out number divisible by 3:"); flush(stdout)
array[array .% 3 .== 0] .= 0
display(array)
print("\nOriginal array after squaring even values in the first three columns:"); flush(stdout)
first_three_column = @view array[:,1:3]
first_three_column[first_three_column .% 2 .== 0] .^= 2
display(array)
Original array:
4×5 Matrix{Int64}: 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19
Selection mask:
4×5 Matrix{Int64}: 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
Selected values:
1×10 adjoint(::Vector{Int64}) with eltype Int64: 1 3 4 6 9 11 12 14 17 19
Original array after zeroing out number divisible by 3:
4×5 Matrix{Int64}: 0 4 8 0 16 1 5 0 13 17 2 0 10 14 0 0 7 11 0 19
Original array after squaring even values in the first three columns:
4×5 Matrix{Int64}: 0 16 64 0 16 1 5 0 13 17 4 0 100 14 0 0 7 11 0 19
>java
NDArray<Integer> array = new BasicIntegerNDArray(4, 4).fillUsingLinearIndices(i -> i);
System.out.println("Original matrix: " + array.contentToString());
NDArray<Integer> subMatrix = new BasicIntegerNDArray(2, 2).fillUsingLinearIndices(i -> -100 - i);
System.out.println("A smaller matrix: " + subMatrix.contentToString());
array.slice("1:3", "1:3").copyFrom(subMatrix);
System.out.println("Original matrix after assigning the smaller matrix to its center: " + array.contentToString());
Original matrix: basic NDArray<Integer>(4 × 4) 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 A smaller matrix: basic NDArray<Integer>(2 × 2) -100 -102 -101 -103 Original matrix after assigning the smaller matrix to its center: basic NDArray<Integer>(4 × 4) 0 4 8 12 1 -100 -102 13 2 -101 -103 14 3 7 11 15
>matlab
disp("Original matrix:")
array = reshape(0:15, 4, 4);
disp(array)
disp("A smaller matrix:")
subMatrix = -100 - reshape(0:3, 2, 2);
disp(subMatrix)
disp("Original matrix after assigning the smaller matrix to its center:")
array(2:3, 2:3) = subMatrix;
disp(array)
Original matrix: 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 A smaller matrix: -100 -102 -101 -103 Original matrix after assigning the smaller matrix to its center: 0 4 8 12 1 -100 -102 13 2 -101 -103 14 3 7 11 15
>python
print("Original matrix:")
array = np.arange(0, 16).reshape(4, 4).T
print_matrix(array)
print("\nA smaller matrix:")
subMatrix = -100 - np.arange(0, 4).reshape(2, 2).T
print_matrix(subMatrix)
print("\nOriginal matrix after assigning the smaller matrix to its center:")
array[1:3,1:3] = subMatrix;
print_matrix(array)
Original matrix: [[ 0 4 8 12] [ 1 5 9 13] [ 2 6 10 14] [ 3 7 11 15]] A smaller matrix: [[-100 -102] [-101 -103]] Original matrix after assigning the smaller matrix to its center: [[ 0 4 8 12] [ 1 -100 -102 13] [ 2 -101 -103 14] [ 3 7 11 15]]
>julia-1.8
print("Original matrix:"); flush(stdout)
array = collect(reshape(0:15, 4, 4))
display(array)
print("\nA smaller matrix:"); flush(stdout)
subMatrix = -100 .- reshape(0:3, 2, 2)
display(subMatrix)
print("\nOriginal matrix after assigning the smaller matrix to its center:"); flush(stdout)
array[2:3,2:3] .= subMatrix;
display(array)
Original matrix:
4×4 Matrix{Int64}: 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15
A smaller matrix:
2×2 Matrix{Int64}: -100 -102 -101 -103
Original matrix after assigning the smaller matrix to its center:
4×4 Matrix{Int64}: 0 4 8 12 1 -100 -102 13 2 -101 -103 14 3 7 11 15
>java
NDArray<Integer> array = new BasicIntegerNDArray(4, 3, 2).fillUsingLinearIndices(i -> i);
System.out.println("Original array: " + array.contentToString());
NDArray<Integer> reshaped = array.reshape(4, 6);
System.out.println("Reshaped array: " + reshaped.contentToString());
reshaped.slice(":2:", ":").fill(-10);
System.out.println("Original array after changing reshaping view: " + array.contentToString());
Original array: basic NDArray<Integer>(4 × 3 × 2) [:, :, 0] = 0 4 8 1 5 9 2 6 10 3 7 11 [:, :, 1] = 12 16 20 13 17 21 14 18 22 15 19 23 Reshaped array: basic NDArrayView<Integer>(4 × 6) 0 4 8 12 16 20 1 5 9 13 17 21 2 6 10 14 18 22 3 7 11 15 19 23 Original array after changing reshaping view: basic NDArray<Integer>(4 × 3 × 2) [:, :, 0] = -10 -10 -10 1 5 9 -10 -10 -10 3 7 11 [:, :, 1] = -10 -10 -10 13 17 21 -10 -10 -10 15 19 23
>matlab
disp("Original array:")
array = reshape(0:23, 4, 3, 2);
disp(array)
disp("Reshaped array:")
reshaped = reshape(array, 4, 6); % At this point, Matlab doesn't create a copy, reshaped is a new reference only
disp(reshaped)
disp("Original array after changing reshaping view: ")
reshaped(1:2:4,:) = -10; % this command creates a copy of the original array with the new shape
disp(array) % therefore, it's not possible to modify original array through the reshaped variable in Matlab
Original array: (:,:,1) = 0 4 8 1 5 9 2 6 10 3 7 11 (:,:,2) = 12 16 20 13 17 21 14 18 22 15 19 23 Reshaped array: 0 4 8 12 16 20 1 5 9 13 17 21 2 6 10 14 18 22 3 7 11 15 19 23 Original array after changing reshaping view: (:,:,1) = 0 4 8 1 5 9 2 6 10 3 7 11 (:,:,2) = 12 16 20 13 17 21 14 18 22 15 19 23
>python
print("Original array:")
array = np.arange(0, 24).reshape(2, 3, 4).transpose((2, 1, 0))
print_matrix(array)
print("\nReshaped array:")
reshaped = array.T.reshape(6, 4).T
print_matrix(reshaped)
print("\nOriginal array after changing reshaping view: ")
reshaped[::2, :] = -10
print_matrix(array)
Original array: [[[ 0 4 8] [ 1 5 9] [ 2 6 10] [ 3 7 11]] [[12 16 20] [13 17 21] [14 18 22] [15 19 23]]] Reshaped array: [[ 0 4 8 12 16 20] [ 1 5 9 13 17 21] [ 2 6 10 14 18 22] [ 3 7 11 15 19 23]] Original array after changing reshaping view: [[[-10 -10 -10] [ 1 5 9] [-10 -10 -10] [ 3 7 11]] [[-10 -10 -10] [ 13 17 21] [-10 -10 -10] [ 15 19 23]]]
>julia-1.8
print("Original array:"); flush(stdout)
array = collect(reshape(0:23, 4, 3, 2))
display(array)
print("Reshaped array:"); flush(stdout)
reshaped = reshape(array, 4, 6)
display(reshaped)
print("\nOriginal array after changing reshaping view: "); flush(stdout)
reshaped[1:2:4,:] .= -10
display(array)
Original array:
4×3×2 Array{Int64, 3}: [:, :, 1] = 0 4 8 1 5 9 2 6 10 3 7 11 [:, :, 2] = 12 16 20 13 17 21 14 18 22 15 19 23
Reshaped array:
4×6 Matrix{Int64}: 0 4 8 12 16 20 1 5 9 13 17 21 2 6 10 14 18 22 3 7 11 15 19 23
Original array after changing reshaping view:
4×3×2 Array{Int64, 3}: [:, :, 1] = -10 -10 -10 1 5 9 -10 -10 -10 3 7 11 [:, :, 2] = -10 -10 -10 13 17 21 -10 -10 -10 15 19 23
Singleton dimension = dimension of size 1
>java
NDArray<Integer> array = new BasicIntegerNDArray(4, 5, 1); // one singleton dimension
System.out.format("Shape of original array: %d × %d × %d%n", array.shape(0), array.shape(1), array.shape(2));
NDArray<Integer> view1 = array.slice(Range.ALL, "3:4", ":"); // two singleton dimensions
System.out.format("Shape of slice: %d × %d × %d%n", view1.shape(0), view1.shape(1), view1.shape(2));
NDArray<Integer> view2 = view1.selectDims(0, 2);
System.out.format("Shape of slice after selecting first and third dimensions: %d × %d%n", view2.shape(0), view2.shape(1));
NDArray<Integer> view3 = view1.dropDims(1);
System.out.format("Shape of slice after dropping the second dimension: %d × %d%n", view3.shape(0), view3.shape(1));
NDArray<Integer> view4 = view1.squeeze();
System.out.format("Shape of slice after squeezing: %d", view4.shape(0));
Shape of original array: 4 × 5 × 1 Shape of slice: 4 × 1 × 1 Shape of slice after selecting first and third dimensions: 4 × 1 Shape of slice after dropping the second dimension: 4 × 1 Shape of slice after squeezing: 4
java.io.PrintStream@d3f4d30
>matlab
array = zeros(4, 5, 1);
fprintf("Shape of original array: %d × %d × %d\n", size(array, 1), size(array, 2), size(array, 3))
view1 = array(:, 2:2, :);
fprintf("Shape of slice: %d × %d × %d\n", size(view1, 1), size(view1, 2), size(view1, 3))
view2 = reshape(view1, size(view1, 1), size(view1, 3));
fprintf("Shape of slice after selecting first and third dimensions: %d × %d\n", size(view2, 1), size(view2, 2))
view3 = squeeze(view1);
fprintf("Shape of slice after squeezing: %s", mat2str(size(view3)))
Shape of original array: 4 × 5 × 1 Shape of slice: 4 × 1 × 1 Shape of slice after selecting first and third dimensions: 4 × 1 Shape of slice after squeezing: [4 1]
>python
array = np.zeros((4, 5, 1))
print("Shape of original array:", " × ".join(map(str, array.shape)))
view1 = array[:, 3:4, :]
print("Shape of slice:", " × ".join(map(str, view1.shape)))
view2 = view1.squeeze(1)
print("Shape of slice after dropping the second dimension:", " × ".join(map(str, view2.shape)))
view3 = view1.squeeze()
print("Shape of slice after squeezing:", " × ".join(map(str, view3.shape)))
Shape of original array: 4 × 5 × 1 Shape of slice: 4 × 1 × 1 Shape of slice after dropping the second dimension: 4 × 1 Shape of slice after squeezing: 4
>julia-1.8
array = zeros(4, 5, 1)
println("Shape of original array: ", join(size(array), " × "))
view1 = array[:, 2:2, :]
println("Shape of slice: ", join(size(view1), " × "))
view2 = dropdims(view1, dims=2)
println("Shape of slice after dropping the second dimension: ", join(size(view2), " × "))
view3 = dropdims(view1, dims=tuple(findall(size(view1) .== 1)...)) # In Julia, there is no direct equivalent of squeeze deliberately
println("Shape of slice after squeezing: ", join(size(view3), " × "))
Shape of original array: 4 × 5 × 1 Shape of slice: 4 × 1 × 1 Shape of slice after dropping the second dimension: 4 × 1 Shape of slice after squeezing: 4
More generally: Change the order of axes/dimensions. In 2D, there are only two permutations of the dimensions: normal order, and the transposed order.
>java
NDArray<Integer> array = new BasicIntegerNDArray(4, 5).fillUsingLinearIndices(i -> i);
System.out.println("Original array: " + array.contentToString());
NDArray<Integer> transposed = array.permuteDims(1,0);
System.out.println("Transposed array: " + transposed.contentToString());
transposed.slice(0, ":").fill(0);
System.out.println("Original array after overwriting the first row in the transposed view: " + array.contentToString());
Original array: basic NDArray<Integer>(4 × 5) 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 Transposed array: basic NDArrayView<Integer>(5 × 4) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Original array after overwriting the first row in the transposed view: basic NDArray<Integer>(4 × 5) 0 4 8 12 16 0 5 9 13 17 0 6 10 14 18 0 7 11 15 19
>matlab
disp("Original array:")
array = reshape(0:19, 4, 5);
disp(array)
disp("Transposed array:")
transposed = array';
transposed = permute(array, [2,1]); % general solution for higher dimensions
disp(transposed)
disp("Original array after overwriting the first row in the transposed view:")
transposed(1,:) = 0; % Matlab creates a copy here seeing that we want to change the content of the original array
disp(array) % Therefore, no changes are reflected in the original array
Original array: 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 Transposed array: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Original array after overwriting the first row in the transposed view: 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19
>python
print("Original array:")
array = np.arange(0, 20).reshape(5, 4).T
print_matrix(array)
print("\nTransposed array:")
transposed = array.T;
transposed = array.transpose((1,0))
print_matrix(transposed)
print("\nOriginal array after overwriting the first row in the transposed view:")
transposed[0,:] = 0
print_matrix(array)
Original array: [[ 0 4 8 12 16] [ 1 5 9 13 17] [ 2 6 10 14 18] [ 3 7 11 15 19]] Transposed array: [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11] [12 13 14 15] [16 17 18 19]] Original array after overwriting the first row in the transposed view: [[ 0 4 8 12 16] [ 0 5 9 13 17] [ 0 6 10 14 18] [ 0 7 11 15 19]]
>julia-1.8
print("Original array:"); flush(stdout)
array = collect(reshape(0:19, 4, 5))
display(array)
print("\nTransposed array:"); flush(stdout)
transposed = permutedims(array, (2,1)) # this version creates a copy!
transposed = array' # this version creates a view!
display(transposed)
print("\nOriginal array after overwriting the first row in the transposed view:"); flush(stdout)
transposed[1,:] .= 0
display(array)
Original array:
4×5 Matrix{Int64}: 0 4 8 12 16 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19
Transposed array:
5×4 adjoint(::Matrix{Int64}) with eltype Int64: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Original array after overwriting the first row in the transposed view:
4×5 Matrix{Int64}: 0 4 8 12 16 0 5 9 13 17 0 6 10 14 18 0 7 11 15 19
>java
NDArray<Integer> original = new BasicIntegerNDArray(4, 5).fillUsingLinearIndices(i -> i + 1);
System.out.println("Original matrix: " + original.contentToString());
NDArray<Integer> increasedBy100 = original.add(100);
System.out.println("New matrix #1 (original + 100): " + increasedBy100.contentToString());
NDArray<Integer> result = increasedBy100.divide(original, 3);
System.out.println("New matrix #2 ((original + 100) / original / 3): " + result.contentToString());
result.multiplyInplace(-1);
System.out.println("New matrix #2 after multiplying in-place each element with -1: " + result.contentToString());
result.subtractInplace(original);
System.out.println("New matrix #2 after subtracting the original matrix in-place: " + result.contentToString());
System.out.println("Original matrix (not changed): " + original.contentToString());
Original matrix: basic NDArray<Integer>(4 × 5) 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 4 8 12 16 20 New matrix #1 (original + 100): basic NDArray<Integer>(4 × 5) 101 105 109 113 117 102 106 110 114 118 103 107 111 115 119 104 108 112 116 120 New matrix #2 ((original + 100) / original / 3): basic NDArray<Integer>(4 × 5) 33 7 4 2 2 17 5 3 2 2 11 5 3 2 2 8 4 3 2 2 New matrix #2 after multiplying in-place each element with -1: basic NDArray<Integer>(4 × 5) -33 -7 -4 -2 -2 -17 -5 -3 -2 -2 -11 -5 -3 -2 -2 -8 -4 -3 -2 -2 New matrix #2 after subtracting the original matrix in-place: basic NDArray<Integer>(4 × 5) -34 -12 -13 -15 -19 -19 -11 -13 -16 -20 -14 -12 -14 -17 -21 -12 -12 -15 -18 -22 Original matrix (not changed): basic NDArray<Integer>(4 × 5) 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 4 8 12 16 20
>matlab
disp("Original matrix:")
original = reshape(1:20, 4, 5);
disp(original)
disp("New matrix #1 (original + 100):")
increasedBy100 = original + 100;
disp(increasedBy100)
disp("New matrix #2 ((original + 100) / original / 3):")
result = floor(increasedBy100 ./ original / 3);
disp(result)
disp("New matrix #2 after multiplying in-place each element with -1:")
result = result * -1;
disp(result)
disp("New matrix #2 after subtracting the original matrix in-place:")
result = result - original;
disp(original)
disp("Original matrix (not changed):")
disp(original)
Original matrix: 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 4 8 12 16 20 New matrix #1 (original + 100): 101 105 109 113 117 102 106 110 114 118 103 107 111 115 119 104 108 112 116 120 New matrix #2 ((original + 100) / original / 3): 33 7 4 2 2 17 5 3 2 2 11 5 3 2 2 8 4 3 2 2 New matrix #2 after multiplying in-place each element with -1: -33 -7 -4 -2 -2 -17 -5 -3 -2 -2 -11 -5 -3 -2 -2 -8 -4 -3 -2 -2 New matrix #2 after subtracting the original matrix in-place: 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 4 8 12 16 20 Original matrix (not changed): 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 4 8 12 16 20
>python
print("Original matrix:")
original = np.arange(1, 21).reshape(5, 4).T;
print_matrix(original)
print("\nNew matrix #1 (original + 100):")
increasedBy100 = original + 100;
print_matrix(increasedBy100)
print("\nNew matrix #2 ((original + 100) / original / 3):")
result = np.floor(increasedBy100 / original / 3);
print_matrix(result)
print("\nNew matrix #2 after multiplying in-place each element with -1:")
result *= -1;
print_matrix(result)
print("\nNew matrix #2 after subtracting the original matrix in-place:")
result -= original;
print_matrix(result)
print("\nOriginal matrix (not changed):")
print_matrix(original)
Original matrix: [[ 1 5 9 13 17] [ 2 6 10 14 18] [ 3 7 11 15 19] [ 4 8 12 16 20]] New matrix #1 (original + 100): [[101 105 109 113 117] [102 106 110 114 118] [103 107 111 115 119] [104 108 112 116 120]] New matrix #2 ((original + 100) / original / 3): [[33. 7. 4. 2. 2.] [17. 5. 3. 2. 2.] [11. 5. 3. 2. 2.] [ 8. 4. 3. 2. 2.]] New matrix #2 after multiplying in-place each element with -1: [[-33. -7. -4. -2. -2.] [-17. -5. -3. -2. -2.] [-11. -5. -3. -2. -2.] [ -8. -4. -3. -2. -2.]] New matrix #2 after subtracting the original matrix in-place: [[-34. -12. -13. -15. -19.] [-19. -11. -13. -16. -20.] [-14. -12. -14. -17. -21.] [-12. -12. -15. -18. -22.]] Original matrix (not changed): [[ 1 5 9 13 17] [ 2 6 10 14 18] [ 3 7 11 15 19] [ 4 8 12 16 20]]
>julia-1.8
print("Original matrix:"); flush(stdout)
original = reshape(1:20, 4, 5)
display(original)
print("\nNew matrix #1 (original + 100):"); flush(stdout)
increasedBy100 = original .+ 100
display(increasedBy100)
print("\nNew matrix #2 ((original + 100) / original / 3):"); flush(stdout)
result = increasedBy100 .÷ original .÷ 3
display(result)
print("\nNew matrix #2 after multiplying in-place each element with -1:"); flush(stdout)
result .*= -1
display(result)
print("\nNew matrix #2 after subtracting the original matrix in-place:"); flush(stdout)
result .-= original
display(result)
print("\nOriginal matrix (not changed):"); flush(stdout)
display(original)
Original matrix:
4×5 reshape(::UnitRange{Int64}, 4, 5) with eltype Int64: 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 4 8 12 16 20
New matrix #1 (original + 100):
4×5 Matrix{Int64}: 101 105 109 113 117 102 106 110 114 118 103 107 111 115 119 104 108 112 116 120
New matrix #2 ((original + 100) / original / 3):
4×5 Matrix{Int64}: 33 7 4 2 2 17 5 3 2 2 11 5 3 2 2 8 4 3 2 2
New matrix #2 after multiplying in-place each element with -1:
4×5 Matrix{Int64}: -33 -7 -4 -2 -2 -17 -5 -3 -2 -2 -11 -5 -3 -2 -2 -8 -4 -3 -2 -2
New matrix #2 after subtracting the original matrix in-place:
4×5 Matrix{Int64}: -34 -12 -13 -15 -19 -19 -11 -13 -16 -20 -14 -12 -14 -17 -21 -12 -12 -15 -18 -22
Original matrix (not changed):
4×5 reshape(::UnitRange{Int64}, 4, 5) with eltype Int64: 1 5 9 13 17 2 6 10 14 18 3 7 11 15 19 4 8 12 16 20
>java
NDArray<Integer> array = new BasicIntegerNDArray(2, 2, 3).fillUsingLinearIndices(i -> i);
System.out.println("Sum of all elements: " + array.sum());
System.out.println("Sum of elements in each slices: " + array.sum(0,1).contentToString());
System.out.println("Product of all elements: " + array.prod());
System.out.println("Product of elements in each slices: " + array.prod(0,1).contentToString());
System.out.println("Squared sum of all elements: " + array.accumulate((acc, newValue) -> acc + newValue * newValue));
// Note that acc is accumulation variable, so it is the squared sum of all previous elements
System.out.println("Sqaerd sum of elements in each slices: " + array.accumulate((acc, newValue) -> acc + newValue * newValue, 0, 1).contentToString());
Sum of all elements: 66 Sum of elements in each slices: basic NDArray<Integer>(3) 6 22 38 Product of all elements: 0 Product of elements in each slices: basic NDArray<Integer>(3) 0 840 7920 Squared sum of all elements: 506 Sqaerd sum of elements in each slices: basic NDArray<Integer>(3) 14 114 310
>matlab
array = reshape(0:11, 2, 2, 3);
fprintf("Sum of all elements: %d\n", sum(array, 'all'))
disp("Sum of elements in each slices:")
disp(squeeze(sum(array, [1 2]))')
fprintf("Product of all elements: %d\n", prod(array, 'all'))
disp("Product of elements in each slices:")
disp(squeeze(prod(array, [1 2]))')
fprintf("Squared sum of all elements: %d\n", sum(array .^ 2, 'all'))
disp("Squared sum of elements in each slices:")
disp(squeeze(sum(array .^ 2, [1 2]))')
Sum of all elements: 66 Sum of elements in each slices: 6 22 38 Product of all elements: 0 Product of elements in each slices: 0 840 7920 Squared sum of all elements: 506 Squared sum of elements in each slices: 14 126 366
>python
array = np.arange(0, 12).reshape(3,2,2).transpose((2,1,0))
print("Sum of all elements:", array.sum())
print("Sum of elements in each slices:", array.sum((0,1)))
print("Sum of all elements:", array.prod())
print("Sum of elements in each slices:", array.prod((0,1)))
print("Sum of all elements:", np.sum(np.square(array)))
print("Sum of elements in each slices:", np.sum(np.square(array), axis=(0, 1)))
Sum of all elements: 66 Sum of elements in each slices: [ 6 22 38] Sum of all elements: 0 Sum of elements in each slices: [ 0 840 7920] Sum of all elements: 506 Sum of elements in each slices: [ 14 126 366]
>julia-1.8
array = reshape(0:11, 2, 2, 3)
println("Sum of all elements: ", sum(array))
println("Sum of elements in each slices: ", dropdims(sum(array, dims=(1,2)), dims=(1,2)))
println("Sum of all elements: ", prod(array))
println("Sum of elements in each slices: ", dropdims(prod(array, dims=(1,2)), dims=(1,2)))
println("Sum of all elements: ", sum(array .^ 2))
println("Sum of elements in each slices: ", dropdims(sum(array .^ 2, dims=(1,2)), dims=(1,2)))
Sum of all elements: 66 Sum of elements in each slices: [6, 22, 38] Sum of all elements: 0 Sum of elements in each slices: [0, 840, 7920] Sum of all elements: 506 Sum of elements in each slices: [14, 126, 366]
>java
import java.util.stream.Collectors;
NDArray<Integer> array = new BasicIntegerNDArray(4, 5).fillUsingLinearIndices(i -> i - 9);
System.out.println("𝐱 = [" + array.stream().map(Object::toString).collect(Collectors.joining(", ")) + "]");
System.out.format("‖𝐱‖₀ = |{xᵢ ≠ 0 : i ∈ 1..N}| = %.0f\t(Number of non-zero elements, 0-pseudonorm)%n", array.norm(0));
System.out.format("‖𝐱‖₁ = ∑|xᵢ| = %.0f\t\t\t(Sum of absolute values, 1-norm)%n", array.norm(1));
System.out.format("‖𝐱‖₂ = (∑ xᵢ²)^½ = %f\t\t(Frobenius norm = Euclidean norm after reshaping array to 1D vector, 2-norm)%n", array.norm(2)); // array.norm() also calculates Euclidean norm
System.out.format("‖x‖ₚ = (∑|xᵢ|ᵖ)^(1/p) = %f\t(p-norm)%n", array.norm(3.5));
System.out.format("‖𝐱‖∞ = max{|xᵢ|} = %.0f\t\t\t(inf-norm)%n", array.norm(Double.POSITIVE_INFINITY));
𝐱 = [-9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ‖𝐱‖₀ = |{xᵢ ≠ 0 : i ∈ 1..N}| = 19 (Number of non-zero elements, 0-pseudonorm) ‖𝐱‖₁ = ∑|xᵢ| = 100 (Sum of absolute values, 1-norm) ‖𝐱‖₂ = (∑ xᵢ²)^½ = 25,884358 (Frobenius norm = Euclidean norm after reshaping array to 1D vector, 2-norm) ‖x‖ₚ = (∑|xᵢ|ᵖ)^(1/p) = 15,371201 (p-norm) ‖𝐱‖∞ = max{|xᵢ|} = 10 (inf-norm)
java.io.PrintStream@d3f4d30
>matlab
array = reshape(-9:10, 4, 5);
disp("𝐱 = ")
disp(array(:)')
fprintf("‖𝐱‖₀ = |{xᵢ ≠ 0 : i ∈ 1..N}| = %.0f\t(Number of non-zero elements, 0-pseudonorm)\n", sum(array ~= 0, 'all'))
fprintf("‖𝐱‖₁ = ∑|xᵢ| = %.0f\t\t\t(Sum of absolute values, 1-norm)\n", sum(abs(array), 'all'))
fprintf("‖𝐱‖₂ = (∑ xᵢ²)^½ = %f\t\t(Frobenius norm = Euclidean norm after reshaping array to 1D vector, 2-norm)\n", norm(array, 'fro'))
fprintf("‖x‖ₚ = (∑|xᵢ|ᵖ)^(1/p) = %f\t(p-norm)\n", sum(abs(array) .^ 3.5, 'all') .^ (1/3.5))
fprintf("‖𝐱‖∞ = max{|xᵢ|} = %.0f\t\t\t(inf-norm)\n", norm(array, 'inf'))
𝐱 = -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 ‖𝐱‖₀ = |{xᵢ ≠ 0 : i ∈ 1..N}| = 19 (Number of non-zero elements, 0-pseudonorm) ‖𝐱‖₁ = ∑|xᵢ| = 100 (Sum of absolute values, 1-norm) ‖𝐱‖₂ = (∑ xᵢ²)^½ = 25.884358 (Frobenius norm = Euclidean norm after reshaping array to 1D vector, 2-norm) ‖x‖ₚ = (∑|xᵢ|ᵖ)^(1/p) = 15.371201 (p-norm) ‖𝐱‖∞ = max{|xᵢ|} = 26 (inf-norm)
>python
from numpy import linalg as LA
array = np.arange(-9, 11).reshape(5,4).T
print("𝐱 =", array.flatten())
print(f"‖𝐱‖₀ = |{{xᵢ ≠ 0 : i ∈ 1..N}}| = {LA.norm(array.flat, 0):.0f}\t(Number of non-zero elements, 0-pseudonorm)")
print(f"‖𝐱‖₁ = ∑|xᵢ| = {LA.norm(array.flat, 1):.0f}\t\t\t(Sum of absolute values, 1-norm)")
print(f"‖𝐱‖₂ = (∑ xᵢ²)^½ = {LA.norm(array.flat, 2):.6f}\t\t(Frobenius norm = Euclidean norm after reshaping array to 1D vector, 2-norm)")
print(f"‖x‖ₚ = (∑|xᵢ|ᵖ)^(1/p) = {LA.norm(array.flat, 3.5):.6f}\t(p-norm)")
print(f"‖𝐱‖∞ = max{{|xᵢ|}} = {LA.norm(array.flat, np.inf):.0f}\t\t\t(inf-norm)")
𝐱 = [-9 -5 -1 3 7 -8 -4 0 4 8 -7 -3 1 5 9 -6 -2 2 6 10] ‖𝐱‖₀ = |{xᵢ ≠ 0 : i ∈ 1..N}| = 19 (Number of non-zero elements, 0-pseudonorm) ‖𝐱‖₁ = ∑|xᵢ| = 100 (Sum of absolute values, 1-norm) ‖𝐱‖₂ = (∑ xᵢ²)^½ = 25.884358 (Frobenius norm = Euclidean norm after reshaping array to 1D vector, 2-norm) ‖x‖ₚ = (∑|xᵢ|ᵖ)^(1/p) = 15.371201 (p-norm) ‖𝐱‖∞ = max{|xᵢ|} = 10 (inf-norm)
>julia-1.8
using LinearAlgebra
array = collect(reshape(-9:10, 4, 5))
println("𝐱 = ", vec(array))
println("‖𝐱‖₀ = |{{xᵢ ≠ 0 : i ∈ 1..N}}| = $(round(Int64, norm(array, 0)))\t(Number of non-zero elements, 0-pseudonorm)")
println("‖𝐱‖₁ = ∑|xᵢ| = $(round(Int64, norm(array, 1)))\t\t\t(Sum of absolute values, 1-norm)")
println("‖𝐱‖₂ = (∑ xᵢ²)^½ = $(round(norm(array, 2), digits=6))\t\t(Frobenius norm = Euclidean norm after reshaping array to 1D vector, 2-norm)")
println("‖x‖ₚ = (∑|xᵢ|ᵖ)^(1/p) = $(round(norm(array, 3.5), digits=6))\t(p-norm)")
println("‖𝐱‖∞ = max{{|xᵢ|}} = $(round(Int64, norm(array, Inf)))\t\t\t(inf-norm)")
𝐱 = [-9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ‖𝐱‖₀ = |{{xᵢ ≠ 0 : i ∈ 1..N}}| = 19 (Number of non-zero elements, 0-pseudonorm) ‖𝐱‖₁ = ∑|xᵢ| = 100 (Sum of absolute values, 1-norm) ‖𝐱‖₂ = (∑ xᵢ²)^½ = 25.884358 (Frobenius norm = Euclidean norm after reshaping array to 1D vector, 2-norm) ‖x‖ₚ = (∑|xᵢ|ᵖ)^(1/p) = 15.371201 (p-norm) ‖𝐱‖∞ = max{{|xᵢ|}} = 10 (inf-norm)
This functionality is fully Java-related; therefore, only Java examples are provided.
Let's create a random matrix filled with values between 0 and 1! We will use this array in the following examples.
>java
NDArray<Double> array = new BasicDoubleNDArray(100,100,20).fillUsingLinearIndices(i -> Math.random());
Alternative for multiple nested loops, and it also makes the parallelization of iterations easy.
Example: Let's print the center coordinates of 3×3 blocks in which the average of values is above 0.85
Conventional Java solution:
>java
import java.util.List;
import java.util.ArrayList;
List<int[]> highIntensityBlocks = new ArrayList<>();
for (int k = 0; k < array.shape(2); k++) {
for (int j = 1; j < array.shape(1) - 1; j++) {
for (int i = 1; i < array.shape(0) - 1; i++) {
var block = array.slice(new Range(i - 1, i + 2), new Range(j - 1, j + 2), k);
if (block.sum() / block.length() > 0.85)
highIntensityBlocks.add(new int[]{ i, j, k });
}
}
}
highIntensityBlocks.stream().map(idx -> String.format("[%d,%d,%d]", idx[0], idx[1], idx[2])).collect(Collectors.joining(", "));
[21,40,0], [32,30,2], [27,96,7], [85,15,11], [72,11,16]
With NDArray's streaming interface:
>java
array.streamCartesianIndices()
//.parallel() // This line is temporarily commented out to print results in the same order as it was above
.filter(idx -> 0 < idx[0] && idx[0] < array.shape(0) - 1 && 0 < idx[1] && idx[1] < array.shape(1) - 1) // discarding the boundary coordinates
.filter(idx -> { // finding the high intensity blocks
var block = array.slice(new Range(idx[0] - 1, idx[0] + 2), new Range(idx[1] - 1, idx[1] + 2), idx[2]);
return block.sum() / block.length() > 0.85;
})
.map(idx -> String.format("[%d,%d,%d]", idx[0], idx[1], idx[2]))
.collect(Collectors.joining(", "));
[21,40,0], [32,30,2], [27,96,7], [85,15,11], [72,11,16]
Example: Let's print the center coordinates of 3×3×20 block in which the average of values is above 0.55
These blocks stretch over the entire range along the third dimension, so there is no need to iterate over the third dimension!
Conventional Java solution:
>java
import java.util.List;
import java.util.ArrayList;
List<int[]> highIntensityBlocks = new ArrayList<>();
for (int j = 1; j < array.shape(1) - 1; j++) {
for (int i = 1; i < array.shape(0) - 1; i++) {
var block = array.slice(new Range(i - 1, i + 2), new Range(j - 1, j + 2), Range.ALL);
if (block.sum() / block.length() > 0.55)
highIntensityBlocks.add(new int[]{ i, j });
}
}
highIntensityBlocks.stream().map(idx -> String.format("[%d,%d]", idx[0], idx[1])).collect(Collectors.joining(", "));
[30,1], [31,3], [7,6], [6,7], [97,12], [36,13], [15,16], [65,19], [22,20], [28,25], [29,25], [57,25], [27,26], [28,26], [29,26], [4,28], [5,28], [6,28], [30,30], [31,30], [15,31], [15,32], [67,32], [63,33], [41,34], [42,34], [41,35], [42,35], [33,36], [34,36], [16,39], [21,39], [73,39], [39,41], [44,41], [73,41], [9,45], [40,52], [31,53], [61,53], [61,54], [11,57], [30,61], [31,61], [10,62], [30,62], [9,63], [17,63], [36,63], [9,64], [10,64], [17,64], [18,64], [36,64], [37,64], [83,64], [84,64], [36,66], [46,67], [47,67], [40,68], [42,68], [80,69], [12,72], [80,76], [45,77], [75,77], [77,77], [6,79], [17,79], [35,79], [17,80], [43,85], [62,86], [63,86], [62,87], [87,88], [53,90], [80,90], [33,92], [30,94], [92,94], [93,94], [94,94], [42,95], [53,95], [62,95], [93,95], [94,95], [88,97], [89,97], [19,98], [86,98], [88,98]
With NDArray's streaming interface:
>java
NDArray.streamCartesianIndices(100, 100)
//.parallel() // This line is temporarily commented out to print results in the same order as it was above
.filter(idx -> 0 < idx[0] && idx[0] < array.shape(0) - 1 && 0 < idx[1] && idx[1] < array.shape(1) - 1) // discarding the boundary coordinates
.filter(idx -> { // finding the high intensity blocks
var block = array.slice(new Range(idx[0] - 1, idx[0] + 2), new Range(idx[1] - 1, idx[1] + 2), Range.ALL);
return block.sum() / block.length() > 0.55;
})
.map(idx -> String.format("[%d,%d]", idx[0], idx[1]))
.collect(Collectors.joining(", "));
[30,1], [31,3], [7,6], [6,7], [97,12], [36,13], [15,16], [65,19], [22,20], [28,25], [29,25], [57,25], [27,26], [28,26], [29,26], [4,28], [5,28], [6,28], [30,30], [31,30], [15,31], [15,32], [67,32], [63,33], [41,34], [42,34], [41,35], [42,35], [33,36], [34,36], [16,39], [21,39], [73,39], [39,41], [44,41], [73,41], [9,45], [40,52], [31,53], [61,53], [61,54], [11,57], [30,61], [31,61], [10,62], [30,62], [9,63], [17,63], [36,63], [9,64], [10,64], [17,64], [18,64], [36,64], [37,64], [83,64], [84,64], [36,66], [46,67], [47,67], [40,68], [42,68], [80,69], [12,72], [80,76], [45,77], [75,77], [77,77], [6,79], [17,79], [35,79], [17,80], [43,85], [62,86], [63,86], [62,87], [87,88], [53,90], [80,90], [33,92], [30,94], [92,94], [93,94], [94,94], [42,95], [53,95], [62,95], [93,95], [94,95], [88,97], [89,97], [19,98], [86,98], [88,98]
Example: Let's calculate the squared sum of every 10th element.
>java
double sum = 0;
for (int i = 0; i < array.length(); i += 10)
sum += array.get(i);
sum
10029.354653395942
>java
array.streamLinearIndices()
.filter(i -> i % 10 == 0)
.mapToObj(i -> array.get(i))
.reduce(0., (a,b) -> a + b);
10029.354653395989
There are convenience functions to iterate slices over some dimensions and apply functions to them, or even reduce over them.
>java
NDArray<Integer> smallArray = new BasicIntegerNDArray(2, 4, 2).fillUsingLinearIndices(i -> i);
System.out.println("Original array: " + smallArray.contentToString());
class Operations {
public static NDArray<Integer> assignFirstCoordinate(NDArray<Integer> slice, int[] index) {
return slice.fill(index[0]);
}
public static NDArray<Integer> assignSecondCoordinate(NDArray<Integer> slice, int[] index) {
return slice.fill(index[1]);
}
public static Integer reduce(NDArray<Integer> slice, int[] index) {
return slice.sum();
}
}
NDArray<Integer> mapped = smallArray.mapOnSlices(Operations::assignFirstCoordinate, 1, 2);
// The same thing with lambda function:
NDArray<Integer> mapped = smallArray.mapOnSlices((slice,idx) -> {
return slice.fill(idx[0]);
}, 1, 2);
System.out.println("Mapped result: " + mapped.contentToString());
mapped.applyOnSlices(Operations::assignSecondCoordinate, 1, 2);
System.out.println("Mapped result after modification: " + mapped.contentToString());
NDArray<Integer> reduced = smallArray.reduceSlices(Operations::reduce, 2);
System.out.println("Reduced along third dimension: " + reduced.contentToString());
Original array: basic NDArray<Integer>(2 × 4 × 2) [:, :, 0] = 0 2 4 6 1 3 5 7 [:, :, 1] = 8 10 12 14 9 11 13 15 Mapped result: basic NDArray<Integer>(2 × 4 × 2) [:, :, 0] = 0 1 2 3 0 1 2 3 [:, :, 1] = 0 1 2 3 0 1 2 3 Mapped result after modification: basic NDArray<Integer>(2 × 4 × 2) [:, :, 0] = 0 0 0 0 0 0 0 0 [:, :, 1] = 1 1 1 1 1 1 1 1 Reduced along third dimension: basic NDArray<Integer>(2 × 4) 8 12 16 20 10 14 18 22
We can convert NDArrays into native arrays of both objects and primitive values.
>java
Double[][][] nativeArray = (Double[][][]) array.toArray();
Double[][][] nativeArray2 = array.toArray(Double[][][]::new);
double[][][] nativeArray2 = array.toArray(double[][][]::new);
It is possible to concatenate arrays (even different data type!) along a specified dimension:
>java
NDArray<Double> concatenated = array.concatenate(2, new BasicDoubleNDArray(100, 100, 50), new BasicIntegerNDArray(100, 100, 30));
String.format("Shape of concatenated array: %d × %d × %d", concatenated.shape(0), concatenated.shape(1), concatenated.shape(2));
Shape of concatenated array: 100 × 100 × 100
This is actually a shorthand for the following expressions:
>java
NDArray<Double> concatenated = new BasicDoubleNDArray(100, 100, 20 + 50 + 30);
concatenated.slice(":", ":", "0:20").copyFrom(array);
concatenated.slice(":", ":", "20:70").copyFrom(new BasicDoubleNDArray(100, 100, 50));
concatenated.slice(":", ":", "70:100").copyFrom(new BasicIntegerNDArray(100, 100, 30));
String.format("Shape of concatenated array: %d × %d × %d", concatenated.shape(0), concatenated.shape(1), concatenated.shape(2));
Shape of concatenated array: 100 × 100 × 100
The equals method compares NDArrays elementwise, and returns true only when all elements are pairwise equal.
When two NDArrays with different data types are compared, it first attemps to cast elements on the right hand side to match the data type on the left, and it performs the comparison afterwards. This means that equals() is not always symmetric (a.equals(b) ↔ b.equals(a))!
>java
NDArray<Double> array1 = new BasicDoubleNDArray(4, 5).fillUsingLinearIndices(i -> i + 0.5);
NDArray<Integer> array2 = new BasicIntegerNDArray(4, 5).fillUsingLinearIndices(i -> i);
System.out.println("array1.equals(array2) == " + (array1.equals(array2) ? "true" : "false")); // double values are casted to int (e.g. 0.5 -> 0)
System.out.println("array2.equals(array1) == " + (array2.equals(array1) ? "true" : "false")); // int values are casted to int (e.g. 0 -> 0.0)
array1.equals(array2) == false array2.equals(array1) == true
Sometimes, we have an array, and we need another one that has same shape and data type, but we don't need to copy all the values. similar()
creates such an array, and leaves default values in each elements (which is 0 for basic NDArrays, but might be uninitialized for other implementations).
>java
NDArray<Integer> array = new BasicIntegerNDArray(4, 5).fillUsingLinearIndices(i -> i);
NDArray<Integer> another = array.similar();
String.format("Shape of 'array' (%d×%d) and 'another' (%d×%d) are equal, but array.equals(another) == " + (array.equals(another) ? "true" : "false"),
array.shape(0), array.shape(1), another.shape(0), another.shape(1));
Shape of 'array' (4×5) and 'another' (4×5) are equal, but array.equals(another) == false
We can easily save an NDArray to a file, and read it back. The file stores all the necessary meta data such that the shape of the array and the data type. We can, however, read saved arrays into NDArrays of any data type, and NDArrays performs the necessary type casting.
There is no constraint on the file extension, .nda
is only my personal preference.
>java
File file = new File("test.nda");
array.writeToFile(file);
NDArray<Double> array2 = BasicDoubleNDArray.readFromFile(file);
NDArray<Integer> array3 = BasicIntegerNDArray.readFromFile(file); // reading into an array of different data type
We use Complex class from Apache math3 library to represent complex values.
Complex NDArrays are no different from simple NDArrays; in fact, ComplexNDArray is a sub-interface of NDArray. Thus, we present only features unique to complex NDArrays. Also, we see no advantage of presenting the parallel solutions in the other languages.
>java
import org.apache.commons.math3.complex.Complex;
import io.github.hakkelt.ndarrays.ComplexNDArray;
import io.github.hakkelt.ndarrays.basic.BasicComplexDoubleNDArray;
>java
ComplexNDArray<Double> zeroArray = new BasicComplexDoubleNDArray(4, 5); // Basic ComplexNDArray initialized with Complex.ZERO by default
System.out.println("Array of zeros: " + zeroArray.contentToString());
ComplexNDArray<Double> twosArray = new BasicComplexDoubleNDArray(4, 5).fill(2);
System.out.println("Array of (real) twos: " + twosArray.contentToString());
ComplexNDArray<Double> constantArray = new BasicComplexDoubleNDArray(4, 5).fill(new Complex(2,-1));
System.out.println("Array filled with complex values: " + constantArray.contentToString());
Array of zeros: basic NDArray<Complex Double>(4 × 5) 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i Array of (real) twos: basic NDArray<Complex Double>(4 × 5) 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i Array filled with complex values: basic NDArray<Complex Double>(4 × 5) 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i 2,00000e+00-1,00000e+00i
It is pretty much the same as it was for real NDArrays but with complex values in this case.
>java
new BasicComplexDoubleNDArray(4, 5) // create array
.fillUsingLinearIndices(i -> new Complex(i, -i * 2)) // initialize
.contentToString() // print content
basic NDArray<Complex Double>(4 × 5) 0,00000e+00+0,00000e+00i 4,00000e+00-8,00000e+00i 8,00000e+00-1,60000e+01i 1,20000e+01-2,40000e+01i 1,60000e+01-3,20000e+01i 1,00000e+00-2,00000e+00i 5,00000e+00-1,00000e+01i 9,00000e+00-1,80000e+01i 1,30000e+01-2,60000e+01i 1,70000e+01-3,40000e+01i 2,00000e+00-4,00000e+00i 6,00000e+00-1,20000e+01i 1,00000e+01-2,00000e+01i 1,40000e+01-2,80000e+01i 1,80000e+01-3,60000e+01i 3,00000e+00-6,00000e+00i 7,00000e+00-1,40000e+01i 1,10000e+01-2,20000e+01i 1,50000e+01-3,00000e+01i 1,90000e+01-3,80000e+01i
>java
new BasicComplexDoubleNDArray(4, 5)
.fillUsingCartesianIndices(idx -> new Complex(idx[0] * idx[0], idx[1] * idx[1]))
.contentToString()
basic NDArray<Complex Double>(4 × 5) 0,00000e+00+0,00000e+00i 0,00000e+00+1,00000e+00i 0,00000e+00+4,00000e+00i 0,00000e+00+9,00000e+00i 0,00000e+00+1,60000e+01i 1,00000e+00+0,00000e+00i 1,00000e+00+1,00000e+00i 1,00000e+00+4,00000e+00i 1,00000e+00+9,00000e+00i 1,00000e+00+1,60000e+01i 4,00000e+00+0,00000e+00i 4,00000e+00+1,00000e+00i 4,00000e+00+4,00000e+00i 4,00000e+00+9,00000e+00i 4,00000e+00+1,60000e+01i 9,00000e+00+0,00000e+00i 9,00000e+00+1,00000e+00i 9,00000e+00+4,00000e+00i 9,00000e+00+9,00000e+00i 9,00000e+00+1,60000e+01i
Note that for technical reason, this initialization is done by factory methods (.of()
static methods, so new
keyword shall not be used)!
>java
int[][] real = new int[4][5];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 5; j++)
real[i][j] = i;
int[][] imag = new int[4][5];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 5; j++)
imag[i][j] = j;
System.out.println("Real-only ComplexNDArray: " + BasicComplexDoubleNDArray.of(real).contentToString());
System.out.println("A truly complex ComplexNDArray: " + BasicComplexDoubleNDArray.of(real, imag).contentToString());
Real-only ComplexNDArray: basic NDArray<Complex Double>(4 × 5) 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 1,00000e+00+0,00000e+00i 1,00000e+00+0,00000e+00i 1,00000e+00+0,00000e+00i 1,00000e+00+0,00000e+00i 1,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i A truly complex ComplexNDArray: basic NDArray<Complex Double>(4 × 5) 0,00000e+00+0,00000e+00i 0,00000e+00+1,00000e+00i 0,00000e+00+2,00000e+00i 0,00000e+00+3,00000e+00i 0,00000e+00+4,00000e+00i 1,00000e+00+0,00000e+00i 1,00000e+00+1,00000e+00i 1,00000e+00+2,00000e+00i 1,00000e+00+3,00000e+00i 1,00000e+00+4,00000e+00i 2,00000e+00+0,00000e+00i 2,00000e+00+1,00000e+00i 2,00000e+00+2,00000e+00i 2,00000e+00+3,00000e+00i 2,00000e+00+4,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+1,00000e+00i 3,00000e+00+2,00000e+00i 3,00000e+00+3,00000e+00i 3,00000e+00+4,00000e+00i
Usually, copy constructor is useful to convert between different types of NDArray.
>java
NDArray<Double> real = new BasicDoubleNDArray(4, 5).fill(3);
NDArray<Double> imag = new BasicDoubleNDArray(4, 5).fill(-1);
System.out.println("Real-only ComplexNDArray: " + new BasicComplexDoubleNDArray(real).contentToString());
System.out.println("A truly complex ComplexNDArray: " + new BasicComplexDoubleNDArray(real, imag).contentToString());
Real-only ComplexNDArray: basic NDArray<Complex Double>(4 × 5) 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i A truly complex ComplexNDArray: basic NDArray<Complex Double>(4 × 5) 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i 3,00000e+00-1,00000e+00i
>java
NDArray<Double> magnitude = new BasicDoubleNDArray(4, 5).fill(3);
NDArray<Double> phase = new BasicDoubleNDArray(4, 5).fill(-1);
BasicComplexDoubleNDArray.ofMagnitudePhase(magnitude, phase).contentToString();
basic NDArray<Complex Double>(4 × 5) 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i 1,62091e+00-2,52441e+00i
This solution is not recommended as we need explicit type casting (Java can only infer NDArray<Complex>
but cannot specialize further).
>java
ComplexNDArray<Double> array = (ComplexNDArray<Double>) IntStream.range(0, 20)
.mapToObj(i -> new Complex(i, -i))
.collect(BasicComplexDoubleNDArray.getCollector(4, 5));
array.contentToString()
basic NDArray<Complex Double>(4 × 5) 0,00000e+00+0,00000e+00i 4,00000e+00-4,00000e+00i 8,00000e+00-8,00000e+00i 1,20000e+01-1,20000e+01i 1,60000e+01-1,60000e+01i 1,00000e+00-1,00000e+00i 5,00000e+00-5,00000e+00i 9,00000e+00-9,00000e+00i 1,30000e+01-1,30000e+01i 1,70000e+01-1,70000e+01i 2,00000e+00-2,00000e+00i 6,00000e+00-6,00000e+00i 1,00000e+01-1,00000e+01i 1,40000e+01-1,40000e+01i 1,80000e+01-1,80000e+01i 3,00000e+00-3,00000e+00i 7,00000e+00-7,00000e+00i 1,10000e+01-1,10000e+01i 1,50000e+01-1,50000e+01i 1,90000e+01-1,90000e+01i
>java
ComplexNDArray<Double> array = new BasicComplexDoubleNDArray(4, 5).fillUsingLinearIndices(i -> new Complex(i, -i));
array.set(new Complex(5), 0, 1); // Cartesian indexing starting from 0
array.set(new Complex(-1, -1), 8); // linear indexing starting from 0
array.setReal(3, 0, 0);
array.setImag(-3, 0, 0);
System.out.println(array.contentToString());
System.out.format("Element at [0,1]: %f + %fi%n", array.get(0, 1).getReal(), array.get(0, 1).getImaginary());
System.out.format("Element at linear index 8: %f + %fi%n", array.get(8).getReal(), array.get(8).getImaginary());
System.out.format("Real component of element at [0,0]: %f%n", array.getReal(0, 1));
System.out.format("Imaginary component of element at [0,0]: %f", array.getReal(0, 1));
basic NDArray<Complex Double>(4 × 5) 3,00000e+00-3,00000e+00i 5,00000e+00+0,00000e+00i -1,00000e+00-1,00000e+00i 1,20000e+01-1,20000e+01i 1,60000e+01-1,60000e+01i 1,00000e+00-1,00000e+00i 5,00000e+00-5,00000e+00i 9,00000e+00-9,00000e+00i 1,30000e+01-1,30000e+01i 1,70000e+01-1,70000e+01i 2,00000e+00-2,00000e+00i 6,00000e+00-6,00000e+00i 1,00000e+01-1,00000e+01i 1,40000e+01-1,40000e+01i 1,80000e+01-1,80000e+01i 3,00000e+00-3,00000e+00i 7,00000e+00-7,00000e+00i 1,10000e+01-1,10000e+01i 1,50000e+01-1,50000e+01i 1,90000e+01-1,90000e+01i Element at [0,1]: 5,000000 + 0,000000i Element at linear index 8: -1,000000 + -1,000000i Real component of element at [0,0]: 5,000000 Imaginary component of element at [0,0]: 5,000000
java.io.PrintStream@d3f4d30
Assign complex sub-matrix:
>java
ComplexNDArray<Double> array = new BasicComplexDoubleNDArray(4, 4).fillUsingLinearIndices(i -> new Complex(i, i));
System.out.println("Original matrix: " + array.contentToString());
ComplexNDArray<Double> complexSubMatrix = new BasicComplexDoubleNDArray(2, 2).fillUsingLinearIndices(i -> new Complex(0, -100 - i));
System.out.println("A smaller matrix: " + complexSubMatrix.contentToString());
array.slice("1:3", "1:3").copyFrom(complexSubMatrix);
System.out.println("Original matrix after assigning the smaller matrix to its center: " + array.contentToString());
Original matrix: basic NDArray<Complex Double>(4 × 4) 0,00000e+00+0,00000e+00i 4,00000e+00+4,00000e+00i 8,00000e+00+8,00000e+00i 1,20000e+01+1,20000e+01i 1,00000e+00+1,00000e+00i 5,00000e+00+5,00000e+00i 9,00000e+00+9,00000e+00i 1,30000e+01+1,30000e+01i 2,00000e+00+2,00000e+00i 6,00000e+00+6,00000e+00i 1,00000e+01+1,00000e+01i 1,40000e+01+1,40000e+01i 3,00000e+00+3,00000e+00i 7,00000e+00+7,00000e+00i 1,10000e+01+1,10000e+01i 1,50000e+01+1,50000e+01i A smaller matrix: basic NDArray<Complex Double>(2 × 2) 0,00000e+00-1,00000e+02i 0,00000e+00-1,02000e+02i 0,00000e+00-1,01000e+02i 0,00000e+00-1,03000e+02i Original matrix after assigning the smaller matrix to its center: basic NDArray<Complex Double>(4 × 4) 0,00000e+00+0,00000e+00i 4,00000e+00+4,00000e+00i 8,00000e+00+8,00000e+00i 1,20000e+01+1,20000e+01i 1,00000e+00+1,00000e+00i 0,00000e+00-1,00000e+02i 0,00000e+00-1,02000e+02i 1,30000e+01+1,30000e+01i 2,00000e+00+2,00000e+00i 0,00000e+00-1,01000e+02i 0,00000e+00-1,03000e+02i 1,40000e+01+1,40000e+01i 3,00000e+00+3,00000e+00i 7,00000e+00+7,00000e+00i 1,10000e+01+1,10000e+01i 1,50000e+01+1,50000e+01i
Assign real sub-matrix:
>java
ComplexNDArray<Double> array = new BasicComplexDoubleNDArray(4, 4).fillUsingLinearIndices(i -> new Complex(i, i));
System.out.println("Original matrix: " + array.contentToString());
NDArray<Double> realSubMatrix = new BasicDoubleNDArray(2, 2).fillUsingLinearIndices(i -> -100. - i);
System.out.println("A smaller matrix: " + realSubMatrix.contentToString());
array.slice("1:3", "1:3").copyFrom(realSubMatrix);
System.out.println("Original matrix after assigning the smaller matrix to its center: " + array.contentToString());
Original matrix: basic NDArray<Complex Double>(4 × 4) 0,00000e+00+0,00000e+00i 4,00000e+00+4,00000e+00i 8,00000e+00+8,00000e+00i 1,20000e+01+1,20000e+01i 1,00000e+00+1,00000e+00i 5,00000e+00+5,00000e+00i 9,00000e+00+9,00000e+00i 1,30000e+01+1,30000e+01i 2,00000e+00+2,00000e+00i 6,00000e+00+6,00000e+00i 1,00000e+01+1,00000e+01i 1,40000e+01+1,40000e+01i 3,00000e+00+3,00000e+00i 7,00000e+00+7,00000e+00i 1,10000e+01+1,10000e+01i 1,50000e+01+1,50000e+01i A smaller matrix: basic NDArray<Double>(2 × 2) -1,00000e+02 -1,02000e+02 -1,01000e+02 -1,03000e+02 Original matrix after assigning the smaller matrix to its center: basic NDArray<Complex Double>(4 × 4) 0,00000e+00+0,00000e+00i 4,00000e+00+4,00000e+00i 8,00000e+00+8,00000e+00i 1,20000e+01+1,20000e+01i 1,00000e+00+1,00000e+00i -1,00000e+02+0,00000e+00i -1,02000e+02+0,00000e+00i 1,30000e+01+1,30000e+01i 2,00000e+00+2,00000e+00i -1,01000e+02+0,00000e+00i -1,03000e+02+0,00000e+00i 1,40000e+01+1,40000e+01i 3,00000e+00+3,00000e+00i 7,00000e+00+7,00000e+00i 1,10000e+01+1,10000e+01i 1,50000e+01+1,50000e+01i
Assign real and imaginary parts:
>java
ComplexNDArray<Double> array = new BasicComplexDoubleNDArray(4, 4).fillUsingLinearIndices(i -> new Complex(i, i));
System.out.println("Original matrix: " + array.contentToString());
NDArray<Double> realSubMatrix = new BasicDoubleNDArray(2, 2).fill(0);
System.out.println("A smaller matrix (real part): " + realSubMatrix.contentToString());
NDArray<Double> imaginarySubMatrix = new BasicDoubleNDArray(2, 2).fillUsingLinearIndices(i -> -100. - i);
System.out.println("A smaller matrix (imaginary part): " + imaginarySubMatrix.contentToString());
array.slice("1:3", "1:3").copyFrom(realSubMatrix, imaginarySubMatrix);
System.out.println("Original matrix after assigning the smaller matrix to its center: " + array.contentToString());
Original matrix: basic NDArray<Complex Double>(4 × 4) 0,00000e+00+0,00000e+00i 4,00000e+00+4,00000e+00i 8,00000e+00+8,00000e+00i 1,20000e+01+1,20000e+01i 1,00000e+00+1,00000e+00i 5,00000e+00+5,00000e+00i 9,00000e+00+9,00000e+00i 1,30000e+01+1,30000e+01i 2,00000e+00+2,00000e+00i 6,00000e+00+6,00000e+00i 1,00000e+01+1,00000e+01i 1,40000e+01+1,40000e+01i 3,00000e+00+3,00000e+00i 7,00000e+00+7,00000e+00i 1,10000e+01+1,10000e+01i 1,50000e+01+1,50000e+01i A smaller matrix (real part): basic NDArray<Double>(2 × 2) 0,00000e+00 0,00000e+00 0,00000e+00 0,00000e+00 A smaller matrix (imaginary part): basic NDArray<Double>(2 × 2) -1,00000e+02 -1,02000e+02 -1,01000e+02 -1,03000e+02 Original matrix after assigning the smaller matrix to its center: basic NDArray<Complex Double>(4 × 4) 0,00000e+00+0,00000e+00i 4,00000e+00+4,00000e+00i 8,00000e+00+8,00000e+00i 1,20000e+01+1,20000e+01i 1,00000e+00+1,00000e+00i 0,00000e+00-1,00000e+02i 0,00000e+00-1,02000e+02i 1,30000e+01+1,30000e+01i 2,00000e+00+2,00000e+00i 0,00000e+00-1,01000e+02i 0,00000e+00-1,03000e+02i 1,40000e+01+1,40000e+01i 3,00000e+00+3,00000e+00i 7,00000e+00+7,00000e+00i 1,10000e+01+1,10000e+01i 1,50000e+01+1,50000e+01i
All arithmetical functions (add, addInplace, multiply, multiplyInplace works, of course, for complex NDArrays).
Additionally there are 4 complex-related functions:
>java
ComplexNDArray<Double> array = new BasicComplexDoubleNDArray(4, 4).fillUsingLinearIndices(i -> new Complex(i, i * 2));
System.out.println("Original matrix: " + array.contentToString());
System.out.println("Magnitude: " + array.abs().contentToString());
System.out.println("Phase/Argument: " + array.argument().contentToString());
System.out.println("Real part: " + array.real().contentToString());
System.out.println("Imaginary part: " + array.imaginary().contentToString());
Original matrix: basic NDArray<Complex Double>(4 × 4) 0,00000e+00+0,00000e+00i 4,00000e+00+8,00000e+00i 8,00000e+00+1,60000e+01i 1,20000e+01+2,40000e+01i 1,00000e+00+2,00000e+00i 5,00000e+00+1,00000e+01i 9,00000e+00+1,80000e+01i 1,30000e+01+2,60000e+01i 2,00000e+00+4,00000e+00i 6,00000e+00+1,20000e+01i 1,00000e+01+2,00000e+01i 1,40000e+01+2,80000e+01i 3,00000e+00+6,00000e+00i 7,00000e+00+1,40000e+01i 1,10000e+01+2,20000e+01i 1,50000e+01+3,00000e+01i Magnitude: basic NDArray<Double>(4 × 4) 0,00000e+00 8,94427e+00 1,78885e+01 2,68328e+01 2,23607e+00 1,11803e+01 2,01246e+01 2,90689e+01 4,47214e+00 1,34164e+01 2,23607e+01 3,13050e+01 6,70820e+00 1,56525e+01 2,45967e+01 3,35410e+01 Phase/Argument: basic NDArray<Double>(4 × 4) 0,00000e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 1,10715e+00 Real part: basic NDArray<Double>(4 × 4) 0,00000e+00 4,00000e+00 8,00000e+00 1,20000e+01 1,00000e+00 5,00000e+00 9,00000e+00 1,30000e+01 2,00000e+00 6,00000e+00 1,00000e+01 1,40000e+01 3,00000e+00 7,00000e+00 1,10000e+01 1,50000e+01 Imaginary part: basic NDArray<Double>(4 × 4) 0,00000e+00 8,00000e+00 1,60000e+01 2,40000e+01 2,00000e+00 1,00000e+01 1,80000e+01 2,60000e+01 4,00000e+00 1,20000e+01 2,00000e+01 2,80000e+01 6,00000e+00 1,40000e+01 2,20000e+01 3,00000e+01
Currently only single and double precision is supported (Float
and Double
). dtype2()
can tell which precision is used by the given array.
>java
import io.github.hakkelt.ndarrays.basic.BasicComplexFloatNDArray;
System.out.println("Precision of BasicComplexFloatNDArray: " + new BasicComplexFloatNDArray(4, 4).dtype2().getSimpleName());
System.out.println("Precision of BasicComplexDoubleNDArray: " + new BasicComplexDoubleNDArray(4, 4).dtype2().getSimpleName());
Precision of BasicComplexFloatNDArray: Float Precision of BasicComplexDoubleNDArray: Double
Functions applyOnSlices
, mapOnSlices
and reduceSlices
work fine on ComplexNDArray
, there is however a little problem with them when used with ComplexNDArray
s: the type of the first parameter (slice) is NDArray<Complex>
instead of ComplexNDArray<...>
meaning that you cannot use complex-specific functions on slices like abs()
, argument()
, real()
, and imaginary()
. Therefore, ComplexNDArray
s have three additional functions:
>java
ComplexNDArray<Double> smallArray = new BasicComplexDoubleNDArray(2, 4, 2).fillUsingLinearIndices(i -> new Complex(i, -i));
System.out.println("Original array: " + smallArray.contentToString());
class Operations {
public static NDArray<?> assignFirstCoordinate(ComplexNDArray<Double> slice, int[] index) {
return slice.real();
}
public static NDArray<?> assignSecondCoordinate(ComplexNDArray<Double> slice, int[] index) {
return slice.imaginary();
}
public static Complex reduce(ComplexNDArray<Double> slice, int[] index) {
return slice.sum();
}
}
ComplexNDArray<Double> mapped = smallArray.mapOnComplexSlices(Operations::assignFirstCoordinate, 1, 2);
// The same thing with lambda function:
ComplexNDArray<Double> mapped = smallArray.mapOnComplexSlices((slice,idx) -> {
return slice.fill(idx[0]);
}, 1, 2);
System.out.println("Mapped result: " + mapped.contentToString());
smallArray.applyOnComplexSlices(Operations::assignSecondCoordinate, 1, 2);
System.out.println("Original array after modification: " + smallArray.contentToString());
ComplexNDArray<Double> reduced = smallArray.reduceComplexSlices(Operations::reduce, 2);
System.out.println("Reduced along third dimension: " + reduced.contentToString());
Original array: basic NDArray<Complex Double>(2 × 4 × 2) [:, :, 0] = 0,00000e+00+0,00000e+00i 2,00000e+00-2,00000e+00i 4,00000e+00-4,00000e+00i 6,00000e+00-6,00000e+00i 1,00000e+00-1,00000e+00i 3,00000e+00-3,00000e+00i 5,00000e+00-5,00000e+00i 7,00000e+00-7,00000e+00i [:, :, 1] = 8,00000e+00-8,00000e+00i 1,00000e+01-1,00000e+01i 1,20000e+01-1,20000e+01i 1,40000e+01-1,40000e+01i 9,00000e+00-9,00000e+00i 1,10000e+01-1,10000e+01i 1,30000e+01-1,30000e+01i 1,50000e+01-1,50000e+01i Mapped result: basic NDArray<Complex Double>(2 × 4 × 2) [:, :, 0] = 0,00000e+00+0,00000e+00i 1,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 1,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i [:, :, 1] = 0,00000e+00+0,00000e+00i 1,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i 0,00000e+00+0,00000e+00i 1,00000e+00+0,00000e+00i 2,00000e+00+0,00000e+00i 3,00000e+00+0,00000e+00i Original array after modification: basic NDArray<Complex Double>(2 × 4 × 2) [:, :, 0] = 0,00000e+00+0,00000e+00i -2,00000e+00+0,00000e+00i -4,00000e+00+0,00000e+00i -6,00000e+00+0,00000e+00i -1,00000e+00+0,00000e+00i -3,00000e+00+0,00000e+00i -5,00000e+00+0,00000e+00i -7,00000e+00+0,00000e+00i [:, :, 1] = -8,00000e+00+0,00000e+00i -1,00000e+01+0,00000e+00i -1,20000e+01+0,00000e+00i -1,40000e+01+0,00000e+00i -9,00000e+00+0,00000e+00i -1,10000e+01+0,00000e+00i -1,30000e+01+0,00000e+00i -1,50000e+01+0,00000e+00i Reduced along third dimension: basic NDArray<Complex Double>(2 × 4) -8,00000e+00+0,00000e+00i -1,20000e+01+0,00000e+00i -1,60000e+01+0,00000e+00i -2,00000e+01+0,00000e+00i -1,00000e+01+0,00000e+00i -1,40000e+01+0,00000e+00i -1,80000e+01+0,00000e+00i -2,20000e+01+0,00000e+00i