Query.java

package neureka.devices;

import neureka.Neureka;
import neureka.backend.api.BackendExtension;
import neureka.math.parsing.ParseUtil;
import neureka.devices.host.CPU;

import java.util.Arrays;
import java.util.stream.Stream;

/**
 *  An internal class which queries the backend for {@link Device} instances based on a provided device
 *  type and an array of search keys!
 *  <br> <br>
 *  <b>Warning: This is an internal class, meaning it should not be used
 *  anywhere but within this library. <br>
 *  This class or its public methods might change or get removed in future versions!</b>
 */
final class Query
{
    private static final double ACCEPTABILITY = 0.42;
    private static final String[] TAKE_FIRST = {"first device","first", "first gpu", "first cpu", "primary", "main", "any", "anything", "something"};
    private static final String[] WANTS_GPU = {"first gpu", "gpu", "nvidia", "amd", "intel", "opencl", "fpga", "radeon", "cuda", "apu", "graphics", "rdna", "rocm", "graphics"};
    private static final String[] WANTS_CPU = {"first cpu", "jvm","native","host","cpu","threaded", "processor", "main processor", "central processor", "central processing unit"};

    private Query() {}


    static <T, D extends Device<T>> D query( Class<D> deviceType, String... searchKeys ) {
        String[] flattened =
            Arrays.stream(searchKeys)
                .flatMap( key -> Arrays.stream(key.split(" or ")) )
                .flatMap( key -> Arrays.stream(key.split("\\|\\|")) )
                .map(String::trim)
                .filter( key -> !key.isEmpty() )
                .flatMap( key -> key.equals("amd") ? Stream.of("amd", "advanced micro devices") : Stream.of(key) )
                .flatMap( key -> key.equals("nvidia") ? Stream.of("nvidia", "nvidia corporation") : Stream.of(key) )
                .toArray(String[]::new);

        return _queryInternal( deviceType, flattened );
    }


    private static <T, D extends Device<T>> D _queryInternal( Class<D> deviceType, String... searchKeys )
    {
        if ( deviceType == CPU.class ) return (D) CPU.get();
        String key;
        if ( searchKeys.length == 0 ) key = "";
        else key = String.join(" ", searchKeys).toLowerCase();

        boolean justTakeFirstOne = Arrays.asList(TAKE_FIRST).contains(key);
        boolean probablyWantsGPU = Arrays.stream(WANTS_GPU).anyMatch(key::contains);
        probablyWantsGPU = probablyWantsGPU || key.equals("first") || key.equals("first device");

        double desireForCPU = Arrays.stream(WANTS_CPU)
                                        .flatMapToDouble(
                                            cpuWord ->
                                                Arrays.stream(searchKeys)
                                                        .mapToDouble(word -> ParseUtil.similarity( word, cpuWord ) )
                                        )
                                        .max()
                                        .orElse(0);

        if ( probablyWantsGPU ) desireForCPU /= 10; // CPU instance is most likely not meant!

        for ( String currentKey : searchKeys )
            for ( BackendExtension extension : Neureka.get().backend().getExtensions() ) {
                BackendExtension.DeviceOption found = extension.find( currentKey );
                if ( found == null           ) continue;
                if ( found.device() == null  ) continue;
                if ( found.confidence() <= 0 ) continue;
                if ( !deviceType.isAssignableFrom( found.device().getClass() ) ) continue;
                if ( found.confidence() > ACCEPTABILITY && found.confidence() > desireForCPU || (justTakeFirstOne && probablyWantsGPU) )
                    return (D) found.device();
            }

        if ( probablyWantsGPU )
            return null; // User wants OpenCL but cannot have it :/
        else if ( deviceType.isAssignableFrom( CPU.class ) && (desireForCPU > ACCEPTABILITY || justTakeFirstOne) )
            return (D) CPU.get();
        else
            return null; // We don't know what the user wants, but we do not have it :/
    }

}