github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/ranked_fusion_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  //go:build integrationTest
    13  
    14  package db
    15  
    16  import (
    17  	"context"
    18  	"encoding/json"
    19  	"fmt"
    20  	"os"
    21  	"testing"
    22  
    23  	"github.com/go-openapi/strfmt"
    24  	"github.com/google/uuid"
    25  	"github.com/sirupsen/logrus"
    26  	"github.com/sirupsen/logrus/hooks/test"
    27  	"github.com/stretchr/testify/mock"
    28  	"github.com/stretchr/testify/require"
    29  	"github.com/weaviate/weaviate/entities/additional"
    30  	"github.com/weaviate/weaviate/entities/dto"
    31  	"github.com/weaviate/weaviate/entities/filters"
    32  	"github.com/weaviate/weaviate/entities/models"
    33  	"github.com/weaviate/weaviate/entities/schema"
    34  	"github.com/weaviate/weaviate/entities/search"
    35  	"github.com/weaviate/weaviate/entities/searchparams"
    36  	"github.com/weaviate/weaviate/entities/storobj"
    37  	enthnsw "github.com/weaviate/weaviate/entities/vectorindex/hnsw"
    38  	"github.com/weaviate/weaviate/usecases/config"
    39  	"github.com/weaviate/weaviate/usecases/modules"
    40  	"github.com/weaviate/weaviate/usecases/traverser"
    41  	"github.com/weaviate/weaviate/usecases/traverser/hybrid"
    42  )
    43  
    44  type TestDoc struct {
    45  	DocID    string
    46  	Document string
    47  }
    48  
    49  type TestQuery struct {
    50  	QueryID        string
    51  	Query          string
    52  	MatchingDocIDs []string
    53  }
    54  
    55  var defaultConfig = config.Config{
    56  	QueryDefaults: config.QueryDefaults{
    57  		Limit: 100,
    58  	},
    59  	QueryMaximumResults: 100,
    60  }
    61  
    62  func SetupStandardTestData(t require.TestingT, repo *DB, schemaGetter *fakeSchemaGetter, logger logrus.FieldLogger, k1, b float32) {
    63  	class := &models.Class{
    64  		VectorIndexConfig:   enthnsw.NewDefaultUserConfig(),
    65  		InvertedIndexConfig: BM25FinvertedConfig(k1, b, "none"),
    66  		Class:               "StandardTest",
    67  		Properties: []*models.Property{
    68  			{
    69  				Name:         "document",
    70  				DataType:     []string{string(schema.DataTypeText)},
    71  				Tokenization: "word",
    72  			},
    73  		},
    74  	}
    75  
    76  	schema := schema.Schema{
    77  		Objects: &models.Schema{
    78  			Classes: []*models.Class{class},
    79  		},
    80  	}
    81  
    82  	schemaGetter.schema = schema
    83  
    84  	migrator := NewMigrator(repo, logger)
    85  	migrator.AddClass(context.Background(), class, schemaGetter.shardState)
    86  
    87  	// Load text from file standard_test_data.json
    88  	// This is a list of 1000 documents from the MEDLINE database
    89  	// Each document is a medical abstract
    90  
    91  	data, _ := os.ReadFile("NFCorpus-Corpus.json")
    92  	var docs []TestDoc
    93  	json.Unmarshal(data, &docs)
    94  
    95  	for i, doc := range docs {
    96  		id := strfmt.UUID(uuid.MustParse(fmt.Sprintf("%032d", i)).String())
    97  
    98  		data := map[string]interface{}{"document": doc.Document, "code": doc.DocID}
    99  		obj := &models.Object{Class: "StandardTest", ID: id, Properties: data, CreationTimeUnix: 1565612833955, LastUpdateTimeUnix: 10000020}
   100  		err := repo.PutObject(context.Background(), obj, nil, nil, nil)
   101  		require.Nil(t, err)
   102  	}
   103  }
   104  
   105  func TestHybrid(t *testing.T) {
   106  	dirName := t.TempDir()
   107  	logger := logrus.New()
   108  	schemaGetter := &fakeSchemaGetter{
   109  		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
   110  		shardState: singleShardState(),
   111  	}
   112  	repo, err := New(logger, Config{
   113  		RootPath:                  dirName,
   114  		QueryMaximumResults:       10000,
   115  		MaxImportGoroutinesFactor: 1,
   116  	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, nil, nil)
   117  	require.Nil(t, err)
   118  	repo.SetSchemaGetter(schemaGetter)
   119  	require.Nil(t, repo.WaitForStartup(context.TODO()))
   120  	defer repo.Shutdown(context.Background())
   121  
   122  	SetupStandardTestData(t, repo, schemaGetter, logger, 1.2, 0.75)
   123  
   124  	idx := repo.GetIndex("StandardTest")
   125  	require.NotNil(t, idx)
   126  
   127  	// Load queries from file standard_test_queries.json
   128  	// This is a list of 100 queries from the MEDLINE database
   129  
   130  	data, _ := os.ReadFile("NFCorpus-Query.json")
   131  	var queries []TestQuery
   132  	json.Unmarshal(data, &queries)
   133  	for _, query := range queries {
   134  		kwr := &searchparams.KeywordRanking{Type: "bm25", Properties: []string{}, Query: query.Query}
   135  		addit := additional.Properties{}
   136  		res, _, _ := idx.objectSearch(context.TODO(), 1000, nil, kwr, nil, nil, addit, nil, "", 0)
   137  
   138  		fmt.Printf("query for %s returned %d results\n", query.Query, len(res))
   139  
   140  	}
   141  }
   142  
   143  func TestBIER(t *testing.T) {
   144  	dirName := t.TempDir()
   145  
   146  	logger := logrus.New()
   147  	schemaGetter := &fakeSchemaGetter{
   148  		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
   149  		shardState: singleShardState(),
   150  	}
   151  	repo, err := New(logger, Config{
   152  		RootPath:                  dirName,
   153  		QueryMaximumResults:       10000,
   154  		MaxImportGoroutinesFactor: 1,
   155  	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, nil, nil)
   156  	require.Nil(t, err)
   157  	repo.SetSchemaGetter(schemaGetter)
   158  	require.Nil(t, repo.WaitForStartup(context.TODO()))
   159  	defer repo.Shutdown(context.Background())
   160  
   161  	SetupStandardTestData(t, repo, schemaGetter, logger, 1.2, 0.75)
   162  
   163  	idx := repo.GetIndex("StandardTest")
   164  	require.NotNil(t, idx)
   165  
   166  	// Load queries from file standard_test_queries.json
   167  	// This is a list of 100 queries from the MEDLINE database
   168  
   169  	data, _ := os.ReadFile("NFCorpus-Query.json")
   170  	var queries []TestQuery
   171  	json.Unmarshal(data, &queries)
   172  	for _, query := range queries {
   173  		kwr := &searchparams.KeywordRanking{Type: "bm25", Properties: []string{}, Query: query.Query}
   174  		addit := additional.Properties{}
   175  		res, _, _ := idx.objectSearch(context.TODO(), 1000, nil, kwr, nil, nil, addit, nil, "", 0)
   176  
   177  		fmt.Printf("query for %s returned %d results\n", query.Query, len(res))
   178  		// fmt.Printf("Results: %v\n", res)
   179  
   180  		//for j, doc := range res {
   181  		//	fmt.Printf("res %v, %v\n", j, doc.Object.GetAdditionalProperty("code"))
   182  		//}
   183  
   184  		//Check the docIDs are the same
   185  		//for j, doc := range res[0:10] {
   186  		//	fmt.Printf("Result: rank %v, docID %v, score %v (%v)\n", j, doc.Object.GetAdditionalProperty("code"), doc.Score(), doc.Object.GetAdditionalProperty("document"))
   187  		//	fmt.Printf("Expected: rank %v, docID %v\n", j, query.MatchingDocIDs[j].Object.GetAdditionalProperty("code"))
   188  		//	require.Equal(t, query.MatchingDocIDs[j], doc.Object.GetAdditionalProperty("code").(string))
   189  		//}
   190  
   191  	}
   192  }
   193  
   194  func addObj(repo *DB, i int, props map[string]interface{}, vec []float32) error {
   195  	id := strfmt.UUID(uuid.MustParse(fmt.Sprintf("%032d", i)).String())
   196  
   197  	obj := &models.Object{Class: "MyClass", ID: id, Properties: props, CreationTimeUnix: 1565612833955, LastUpdateTimeUnix: 10000020}
   198  	vector := vec
   199  	err := repo.PutObject(context.Background(), obj, vector, nil, nil)
   200  	return err
   201  }
   202  
   203  func SetupFusionClass(t require.TestingT, repo *DB, schemaGetter *fakeSchemaGetter, logger logrus.FieldLogger, k1, b float32) *models.Class {
   204  	class := &models.Class{
   205  		VectorIndexConfig:   enthnsw.NewDefaultUserConfig(),
   206  		InvertedIndexConfig: BM25FinvertedConfig(k1, b, "none"),
   207  		Class:               "MyClass",
   208  		Properties: []*models.Property{
   209  			{
   210  				Name:         "title",
   211  				DataType:     schema.DataTypeText.PropString(),
   212  				Tokenization: models.PropertyTokenizationWord,
   213  			},
   214  			{
   215  				Name:         "description",
   216  				DataType:     schema.DataTypeText.PropString(),
   217  				Tokenization: models.PropertyTokenizationWord,
   218  			},
   219  		},
   220  	}
   221  
   222  	schema := schema.Schema{
   223  		Objects: &models.Schema{
   224  			Classes: []*models.Class{class},
   225  		},
   226  	}
   227  
   228  	schemaGetter.schema = schema
   229  
   230  	migrator := NewMigrator(repo, logger)
   231  	migrator.AddClass(context.Background(), class, schemaGetter.shardState)
   232  
   233  	addObj(repo, 0, map[string]interface{}{"title": "Our journey to BM25F", "description": "This is how we get to BM25F"}, []float32{
   234  		-0.04488207, -0.32971063, 0.23568298, 0.004971265, -0.12588727, 0.0036464946, -0.6209942, -0.23247992, -0.19338948, 0.3544481, 0.00050193403, 0.36604947, -0.13813384, -0.126298, 0.05640272, -0.36604303, -0.10976448, 0.196644, -0.02206351, -0.27649152, -0.0050981278, 0.020616557, 0.14605781, 0.093781, -0.25838777, -0.5038474, -0.46846977, 0.13360861, -0.15851927, 0.55075127, 0.34870508, 0.085248806, -0.02763206, -0.07068178, -0.26878145, 0.2814703, -0.33317414, 0.48958343, -0.39648432, 0.606744, 0.12882654, 0.07246548, 0.54059577, 0.19526751, 0.85892624, 0.1485534, 0.19790995, -0.34280643, 0.27512825, 0.105886005, 0.030610563, 0.3811836, 0.18384686, 0.29538825, -0.020791993, -0.31372088, -0.08811446, -0.12979206, 0.30209363, -0.14561261, 0.22077207, -0.40219122, -0.40567216, -0.08740993, -0.31625694, -0.18109407, 0.5411316, -0.09015073, 0.22272661, 0.13575949, -0.36186692, -0.02766613, -0.22463024, 0.15271285, 0.304631, -0.57313913, 0.31346974, -0.118818894, -0.36198893, 0.24609627, 0.47406524, -0.55662453, 0.37812573, -0.2959746, 0.6146945, -0.3934654, 0.30840993, -0.24944904, -0.2063059, -0.48078862, -0.08967737, -0.1273727, 0.1587198, -0.44776592, 0.0048942068, 0.18738478, -0.4544592, -0.4755225, 0.2156486, 0.39935833, 0.25160903, -0.35463294, 0.60699236, -0.12445828, -0.029569108, -0.4983043, 0.44752246, -0.2340386, -0.27559096, 0.67984164, 0.51470226, -0.5285723, 0.0024457057, -0.20425095, -0.4915065, -0.3221788, -0.15558766, 0.20102327, 0.23525643, 0.28365672, 0.58687097, 0.3190138, -0.31130886, 0.053733524, -0.10361888, -0.2598206, 1.5977107, 0.60224503, 0.0074084206, 0.17191416, 0.5619663, 0.41178456, 0.27006987, -0.5354418, -0.054428957, 0.6849038, -0.024342017, 0.43103293, -0.22892271, 0.036829337, -0.103084944, -0.2021301, -0.11352237, -0.17110321, 0.76075333, -0.1755375, 0.029183118, -0.34927735, 0.22040148, -0.18136469, 0.16048056, 0.34151044, -0.048658744, 0.03941434, 0.45190382, 0.103645615, 0.10437423, -0.086864054, 0.523172, -0.59672165, -0.1225319, -0.5800122, 0.2197229, 0.49325037, 0.30607533, 0.012414166, 0.1539727, -0.60095996, 0.05142522, 0.021675617, -0.54661363, -0.0050268047, -0.507448, -0.04522115, -0.77988946, 0.10536073, 0.099219516, 0.40711993, 0.27353838, 0.1728696, -0.4171313, -1.3076599, -0.19778727, -0.23201689, 0.40729725, -0.28640944, 0.06354561, -0.3877251, -0.7938625, 0.29908186, -0.24450836, -0.22622268, 0.32792783, 0.28376722, -0.3685573, 0.031423382, -0.012464195, 0.2254249, 0.26994115, -0.19821979, -0.24086252, 0.24454598, 0.30043048, -0.627896, -0.3355214, -0.14054148, 0.50488055, -0.073988594, -0.31053177, 0.36260405, -0.56093204, 0.12066587, -0.47301888, 0.88418764, 0.09010807, -0.10899238, 0.62317103, 0.27237964, -0.604178, 0.0067386073, 0.1370205, -0.094664395, 0.3479645, 0.25092986, 0.16948108, -0.20874223, -0.54980844, -0.100548536, 0.47177002, -0.4981452, 0.1815202, -0.80878633, -0.076736815, 0.43152434, -0.43210435, -0.28010413, 0.1249095, 0.385616, -0.2984289, -0.006841246, 0.3496464, -0.33298343, 0.06344994, 0.37393335, 0.18608452, -0.10631552, -0.40111285, -0.146849, -0.04161288, -0.31621853, -0.06889858, 0.13343252, -0.11599523, 0.5377954, 0.25938663, -0.43172404, -0.7476662, -0.54316807, 0.0029651164, 0.09958581, 0.0730254, 0.22785394, 0.3276773, 0.01816153, 0.094938636, 0.71604383, -0.09648144, -0.0035640472, -0.5383972, 0.28588042, 0.7625968, -0.22359839, 0.17167832, -0.06235203, -0.32480234, -0.18599075, 0.1570872, -0.06470149, -0.029198159, 0.23251827, 0.100047514, -0.06314679, 0.6390605, -0.06232509, 0.76272035, 0.2975126, 0.15871438, 0.18222457, -0.548036, 0.23633306, -0.17981203, 0.023965335, 0.24478278, -0.21601695, -0.108217336, 0.05834005, 0.3718355, 0.0970174, 0.04476983, -0.118143275,
   235  	})
   236  
   237  	addObj(repo, 1, map[string]interface{}{"title": "Our peanuts to BM25F", "description": "This is how we get to BM25F"}, []float32{0.11676752, -0.4837953, -0.06559026, 0.3242706, 0.08680799, -0.30777612, -0.22926088, 0.01667141, 0.31844103, 0.4666344, 0.417305, 0.06108997, -0.0740552, 0.14234918, 0.06823654, 0.16182217, -0.012199775, -0.17269811, -0.16104576, -0.09208117, 0.063624315, 0.3113634, -0.3830663, 0.05831715, -0.14125349, -0.26962206, -0.0696671, -0.013111545, 0.20097807, 0.033809602, -0.048573885, 0.46815604, 0.32582077, 0.32308698, 0.20355524, -0.08757271, 0.17099291, 0.31500003, -0.05445185, 0.7712824, -0.2096038, 0.28787872, 0.10871067, -0.3266944, -0.1633618, 0.34630018, -0.15387866, -0.45506623, -0.21508889, -0.19249445, -0.28801772, -0.2694916, -0.18476918, -0.12890251, -0.29947013, 0.0008435306, -0.06490287, -0.006560939, 0.24637267, -0.111215346, 0.3775517, -0.82433224, -0.3179537, 0.022306278, 0.19248968, -0.1701471, 0.052865, -0.044782564, -0.10222186, 0.09571932, -0.19251339, 0.241193, -0.13216764, -0.19301765, 0.46628228, -0.29973802, 0.0030274116, 0.01664786, 0.1216316, 0.12837356, -0.048461247, -0.56439394, 0.06110007, 0.102808535, 0.63137263, -0.13134736, 0.41365498, -0.113528065, -0.06924132, 0.1076709, -0.06833764, 0.31522226, 0.13445137, -0.16227263, -0.15102008, 0.23768687, -0.41108298, -0.473573, -0.35702798, 0.21465969, -0.30590045, -0.26616427, 0.7287231, -0.036261655, -0.34903425, -0.1396425, -0.022058574, -0.33956096, -0.3359471, -0.035496157, -0.1786069, -0.0857123, -0.0845917, 0.13232024, -0.02890402, -0.45281035, -0.026353035, -0.39124215, -0.15753527, -0.075793914, 0.35795033, 0.35925874, -0.1423145, -0.0969307, 0.08920737, 0.15772092, 1.3536518, 0.29779792, -0.05407743, -0.048793554, 0.12263066, -0.06248072, -0.49598575, -0.46484944, -0.31050035, 0.6283043, 0.5242193, 0.25987545, -0.2584134, 0.32898954, 0.014580286, 0.14016634, -0.010093123, -0.22610027, -0.029830063, 0.18112054, 0.020298548, -0.025797658, -0.40394786, -0.17097965, 0.11640611, 0.29304397, -0.27026933, -0.14832975, -0.099585906, 0.4554175, -0.0018298444, 0.23190805, -0.65866566, -0.09366216, -0.7000203, 0.004698127, -0.17523476, -0.34830904, -0.16284281, 0.15495956, 0.5772887, 0.048939474, -0.12923703, -0.236143, -0.03874896, 0.2960667, 0.029154046, 0.42814374, -0.4332385, -0.31293675, -0.10682973, -0.12069777, 0.071893886, 0.06644212, -0.46342105, -0.8599067, 0.017380634, -0.38347453, 0.14165273, -0.08906643, -0.06801824, 0.19660597, -0.06807183, 0.33882818, 0.044932134, 0.27550527, 0.2308957, -0.101730466, -0.19064885, -0.015364495, 0.0149245, 0.24177131, -0.15636654, -0.002376896, -0.6399841, 0.14845476, 0.46339074, 0.036926877, -0.067630276, 0.289784, 0.15529989, -0.5235124, 0.50196457, -0.004536148, -0.3716798, 0.047304608, -0.027990041, 0.15901157, 0.021176483, 0.35387334, 0.4457043, 0.094738215, 0.08722517, 0.0450516, 0.1739127, -0.2606226, 0.035999063, -0.12919275, -0.11809982, -0.20865, -0.6917279, 0.093973815, -0.38069052, -0.114874505, -0.3051481, -0.357749, 0.48254266, 0.31795567, 0.37491056, 0.0047062743, -0.1265727, 0.51655954, 0.1622121, 0.39811996, -0.002116253, -0.375531, 0.6347343, 0.14833164, 0.032251768, 0.021101426, -0.34346518, 0.22451165, 0.028649824, -0.04794777, 0.056036226, 0.14179966, 0.32724753, 0.17185552, 0.2504634, -0.05013007, -0.31430584, -0.22200464, -0.508279, -0.10017326, 0.16302426, -0.09568865, 0.05985463, -0.22916546, -0.084666654, -0.15271503, -0.24385636, -0.028514259, -0.33194387, -0.17132543, -0.1474212, -0.18526097, 0.2198915, -0.1689729, -0.19907063, 0.19941927, -0.47478884, 0.0695081, 0.3741401, 0.19423902, 0.085894205, -0.53214043, 0.33309302, 0.18701339, 0.23461546, -0.14038202, 0.07201847, 0.3462437, 0.1640635, 0.07200127, -0.09130982, 0.3868172, -0.09754013, 0.040958565, -0.18743117, 0.14117524, -0.18739408, 0.13669269, -0.09902989, -0.16762646})
   238  
   239  	addObj(repo, 2, map[string]interface{}{"title": "Elephant Parade", "description": "Elephants elephants elephant"}, []float32{-0.04488207, -0.32971063, 0.23568298, 0.004971265, -0.12588727, 0.0036464946, -0.6209942, -0.23247992, -0.19338948, 0.3544481, 0.00050193403, 0.36604947, -0.13813384, -0.126298, 0.05640272, -0.36604303, -0.10976448, 0.196644, -0.02206351, -0.27649152, -0.0050981278, 0.020616557, 0.14605781, 0.093781, -0.25838777, -0.5038474, -0.46846977, 0.13360861, -0.15851927, 0.55075127, 0.34870508, 0.085248806, -0.02763206, -0.07068178, -0.26878145, 0.2814703, -0.33317414, 0.48958343, -0.39648432, 0.606744, 0.12882654, 0.07246548, 0.54059577, 0.19526751, 0.85892624, 0.1485534, 0.19790995, -0.34280643, 0.27512825, 0.105886005, 0.030610563, 0.3811836, 0.18384686, 0.29538825, -0.020791993, -0.31372088, -0.08811446, -0.12979206, 0.30209363, -0.14561261, 0.22077207, -0.40219122, -0.40567216, -0.08740993, -0.31625694, -0.18109407, 0.5411316, -0.09015073, 0.22272661, 0.13575949, -0.36186692, -0.02766613, -0.22463024, 0.15271285, 0.304631, -0.57313913, 0.31346974, -0.118818894, -0.36198893, 0.24609627, 0.47406524, -0.55662453, 0.37812573, -0.2959746, 0.6146945, -0.3934654, 0.30840993, -0.24944904, -0.2063059, -0.48078862, -0.08967737, -0.1273727, 0.1587198, -0.44776592, 0.0048942068, 0.18738478, -0.4544592, -0.4755225, 0.2156486, 0.39935833, 0.25160903, -0.35463294, 0.60699236, -0.12445828, -0.029569108, -0.4983043, 0.44752246, -0.2340386, -0.27559096, 0.67984164, 0.51470226, -0.5285723, 0.0024457057, -0.20425095, -0.4915065, -0.3221788, -0.15558766, 0.20102327, 0.23525643, 0.28365672, 0.58687097, 0.3190138, -0.31130886, 0.053733524, -0.10361888, -0.2598206, 1.5977107, 0.60224503, 0.0074084206, 0.17191416, 0.5619663, 0.41178456, 0.27006987, -0.5354418, -0.054428957, 0.6849038, -0.024342017, 0.43103293, -0.22892271, 0.036829337, -0.103084944, -0.2021301, -0.11352237, -0.17110321, 0.76075333, -0.1755375, 0.029183118, -0.34927735, 0.22040148, -0.18136469, 0.16048056, 0.34151044, -0.048658744, 0.03941434, 0.45190382, 0.103645615, 0.10437423, -0.086864054, 0.523172, -0.59672165, -0.1225319, -0.5800122, 0.2197229, 0.49325037, 0.30607533, 0.012414166, 0.1539727, -0.60095996, 0.05142522, 0.021675617, -0.54661363, -0.0050268047, -0.507448, -0.04522115, -0.77988946, 0.10536073, 0.099219516, 0.40711993, 0.27353838, 0.1728696, -0.4171313, -1.3076599, -0.19778727, -0.23201689, 0.40729725, -0.28640944, 0.06354561, -0.3877251, -0.7938625, 0.29908186, -0.24450836, -0.22622268, 0.32792783, 0.28376722, -0.3685573, 0.031423382, -0.012464195, 0.2254249, 0.26994115, -0.19821979, -0.24086252, 0.24454598, 0.30043048, -0.627896, -0.3355214, -0.14054148, 0.50488055, -0.073988594, -0.31053177, 0.36260405, -0.56093204, 0.12066587, -0.47301888, 0.88418764, 0.09010807, -0.10899238, 0.62317103, 0.27237964, -0.604178, 0.0067386073, 0.1370205, -0.094664395, 0.3479645, 0.25092986, 0.16948108, -0.20874223, -0.54980844, -0.100548536, 0.47177002, -0.4981452, 0.1815202, -0.80878633, -0.076736815, 0.43152434, -0.43210435, -0.28010413, 0.1249095, 0.385616, -0.2984289, -0.006841246, 0.3496464, -0.33298343, 0.06344994, 0.37393335, 0.18608452, -0.10631552, -0.40111285, -0.146849, -0.04161288, -0.31621853, -0.06889858, 0.13343252, -0.11599523, 0.5377954, 0.25938663, -0.43172404, -0.7476662, -0.54316807, 0.0029651164, 0.09958581, 0.0730254, 0.22785394, 0.3276773, 0.01816153, 0.094938636, 0.71604383, -0.09648144, -0.0035640472, -0.5383972, 0.28588042, 0.7625968, -0.22359839, 0.17167832, -0.06235203, -0.32480234, -0.18599075, 0.1570872, -0.06470149, -0.029198159, 0.23251827, 0.100047514, -0.06314679, 0.6390605, -0.06232509, 0.76272035, 0.2975126, 0.15871438, 0.18222457, -0.548036, 0.23633306, -0.17981203, 0.023965335, 0.24478278, -0.21601695, -0.108217336, 0.05834005, 0.3718355, 0.0970174, 0.04476983, -0.118143275})
   240  
   241  	return class
   242  }
   243  
   244  func TestRFJourney(t *testing.T) {
   245  	dirName := t.TempDir()
   246  
   247  	logger := logrus.New()
   248  	schemaGetter := &fakeSchemaGetter{
   249  		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
   250  		shardState: singleShardState(),
   251  	}
   252  	repo, err := New(logger, Config{
   253  		RootPath:                  dirName,
   254  		QueryMaximumResults:       10000,
   255  		MaxImportGoroutinesFactor: 1,
   256  		QueryLimit:                20,
   257  	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, nil, nil)
   258  	require.Nil(t, err)
   259  	repo.SetSchemaGetter(schemaGetter)
   260  	require.Nil(t, repo.WaitForStartup(context.TODO()))
   261  	defer repo.Shutdown(context.Background())
   262  
   263  	class := SetupFusionClass(t, repo, schemaGetter, logger, 1.2, 0.75)
   264  	idx := repo.GetIndex("MyClass")
   265  	require.NotNil(t, idx)
   266  	docId1 := uint64(1)
   267  	docId2 := uint64(2)
   268  	docId3 := uint64(3)
   269  
   270  	doc1 := &search.Result{
   271  		ID:    strfmt.UUID("e6f7e8b1-ac53-48eb-b6e4-cbe67396bcfa"),
   272  		DocID: &docId1,
   273  		Schema: map[string]interface{}{
   274  			"title": "peanuts",
   275  		},
   276  		Vector: []float32{0.1, 0.2, 0.3, 0.4, 0.5},
   277  		Score:  0.1,
   278  	}
   279  
   280  	doc2 := &search.Result{
   281  		ID:    strfmt.UUID("2b7a8bc9-29d9-4cc8-b145-a0baf5fc231d"),
   282  		DocID: &docId2,
   283  		Schema: map[string]interface{}{
   284  			"title": "journey",
   285  		},
   286  		Vector: []float32{0.5, 0.4, 0.3, 0.3, 0.1},
   287  		Score:  0.2,
   288  	}
   289  
   290  	doc3 := &search.Result{
   291  		ID:    strfmt.UUID("dddddddd-29d9-4cc8-b145-a0baf5fc231d"),
   292  		DocID: &docId3,
   293  		Schema: map[string]interface{}{
   294  			"title": "alalala",
   295  		},
   296  		Vector: []float32{0.5, 0.4, 0.3, 0.3, 0.1},
   297  		Score:  0.2,
   298  	}
   299  
   300  	resultSet1 := []*search.Result{doc1, doc2, doc3}
   301  	resultSet2 := []*search.Result{doc2, doc1, doc3}
   302  
   303  	t.Run("Fusion Reciprocal", func(t *testing.T) {
   304  		results := hybrid.FusionRanked([]float64{0.4, 0.6},
   305  			[][]*search.Result{resultSet1, resultSet2}, []string{"set1", "set2"})
   306  		fmt.Println("--- Start results for Fusion Reciprocal ---")
   307  		for _, result := range results {
   308  			schema := result.Schema.(map[string]interface{})
   309  			fmt.Println(schema["title"], result.ID, result.Score)
   310  		}
   311  		require.Equal(t, 3, len(results))
   312  		require.Equal(t, resultSet2[0].ID, results[0].ID)
   313  		require.Equal(t, resultSet2[1].ID, results[1].ID)
   314  		require.Equal(t, resultSet2[2].ID, results[2].ID)
   315  		require.Equal(t, float32(0.016557377), results[0].Score)
   316  		require.Equal(t, float32(0.016502732), results[1].Score)
   317  	})
   318  
   319  	t.Run("Fusion Reciprocal 2", func(t *testing.T) {
   320  		results := hybrid.FusionRanked([]float64{0.8, 0.2},
   321  			[][]*search.Result{resultSet1, resultSet2}, []string{"set1", "set2"})
   322  		fmt.Println("--- Start results for Fusion Reciprocal ---")
   323  		for _, result := range results {
   324  			schema := result.Schema.(map[string]interface{})
   325  			fmt.Println(schema["title"], result.ID, result.Score)
   326  		}
   327  		require.Equal(t, 3, len(results))
   328  		require.Equal(t, resultSet2[0].ID, results[1].ID)
   329  		require.Equal(t, resultSet2[1].ID, results[0].ID)
   330  		require.Equal(t, resultSet2[2].ID, results[2].ID)
   331  		require.Equal(t, float32(0.016612021), results[0].Score)
   332  		require.Equal(t, float32(0.016448088), results[1].Score)
   333  	})
   334  
   335  	t.Run("Vector Only", func(t *testing.T) {
   336  		results := hybrid.FusionRanked([]float64{0.0, 1.0},
   337  			[][]*search.Result{resultSet1, resultSet2}, []string{"set1", "set2"})
   338  		fmt.Println("--- Start results for Fusion Reciprocal ---")
   339  		for _, result := range results {
   340  			schema := result.Schema.(map[string]interface{})
   341  			fmt.Println(schema["title"], result.ID, result.Score)
   342  		}
   343  		require.Equal(t, 3, len(results))
   344  		require.Equal(t, resultSet2[0].ID, results[0].ID)
   345  		require.Equal(t, resultSet2[1].ID, results[1].ID)
   346  		require.Equal(t, resultSet2[2].ID, results[2].ID)
   347  		require.Equal(t, float32(0.016666668), results[0].Score)
   348  		require.Equal(t, float32(0.016393442), results[1].Score)
   349  	})
   350  
   351  	t.Run("BM25 only", func(t *testing.T) {
   352  		results := hybrid.FusionRanked([]float64{1.0, 0.0},
   353  			[][]*search.Result{resultSet1, resultSet2}, []string{"set1", "set2"})
   354  		fmt.Println("--- Start results for Fusion Reciprocal ---")
   355  		for _, result := range results {
   356  			schema := result.Schema.(map[string]interface{})
   357  			fmt.Println(schema["title"], result.ID, result.Score)
   358  		}
   359  		require.Equal(t, 3, len(results))
   360  		require.Equal(t, resultSet1[0].ID, results[0].ID)
   361  		require.Equal(t, resultSet1[1].ID, results[1].ID)
   362  		require.Equal(t, resultSet1[2].ID, results[2].ID)
   363  		require.Equal(t, float32(0.016666668), results[0].Score)
   364  		require.Equal(t, float32(0.016393442), results[1].Score)
   365  	})
   366  
   367  	// Check basic search with one property
   368  	results_set_1, err := repo.VectorSearch(context.TODO(), dto.GetParams{
   369  		ClassName:    "MyClass",
   370  		SearchVector: peanutsVector(),
   371  		Pagination: &filters.Pagination{
   372  			Offset: 0,
   373  			Limit:  6,
   374  		},
   375  	})
   376  
   377  	require.Nil(t, err)
   378  	results_set_2, err := repo.VectorSearch(context.TODO(), dto.GetParams{
   379  		ClassName:    "MyClass",
   380  		SearchVector: journeyVector(),
   381  		Pagination: &filters.Pagination{
   382  			Offset: 0,
   383  			Limit:  6,
   384  		},
   385  	})
   386  	require.Nil(t, err)
   387  
   388  	// convert search.Result to hybrid.Result
   389  	var results_set_1_hybrid []*search.Result
   390  	for _, r := range results_set_1 {
   391  		// parse the last 12 digits of the id to get the uint64
   392  
   393  		results_set_1_hybrid = append(results_set_1_hybrid, &r)
   394  	}
   395  
   396  	var results_set_2_hybrid []*search.Result
   397  	for _, r := range results_set_2 {
   398  		results_set_2_hybrid = append(results_set_2_hybrid, &r)
   399  	}
   400  
   401  	res := hybrid.FusionRanked([]float64{0.2, 0.8}, [][]*search.Result{results_set_1_hybrid, results_set_2_hybrid}, []string{"set1", "set2"})
   402  	fmt.Println("--- Start results for Fusion Reciprocal (", len(res), ")---")
   403  	for _, r := range res {
   404  
   405  		schema := r.Schema.(map[string]interface{})
   406  		title := schema["title"].(string)
   407  		description := schema["description"].(string)
   408  		fmt.Printf("Result id: %v, score: %v, title: %v, description: %v, additional %+v\n", r.ID, r.Score, title, description, r.AdditionalProperties)
   409  	}
   410  
   411  	require.Equal(t, "00000000-0000-0000-0000-000000000000", string(res[0].ID))
   412  
   413  	t.Run("Hybrid", func(t *testing.T) {
   414  		params := dto.GetParams{
   415  			ClassName: "MyClass",
   416  			HybridSearch: &searchparams.HybridSearch{
   417  				Query:  "elephant",
   418  				Vector: elephantVector(),
   419  				Alpha:  0.5,
   420  			},
   421  			Pagination: &filters.Pagination{
   422  				Offset: 0,
   423  				Limit:  6,
   424  			},
   425  		}
   426  
   427  		prov := modules.NewProvider()
   428  		prov.SetClassDefaults(class)
   429  
   430  		metrics := &fakeMetrics{}
   431  		log, _ := test.NewNullLogger()
   432  		explorer := traverser.NewExplorer(repo, log, prov, metrics, defaultConfig)
   433  		explorer.SetSchemaGetter(schemaGetter)
   434  		hybridResults, err := explorer.Hybrid(context.TODO(), params)
   435  		require.Nil(t, err)
   436  
   437  		fmt.Println("--- Start results for hybrid ---")
   438  		for _, r := range hybridResults {
   439  			schema := r.Schema.(map[string]interface{})
   440  			title := schema["title"].(string)
   441  			description := schema["description"].(string)
   442  			fmt.Printf("Result id: %v, score: %v, title: %v, description: %v, additional %+v\n", r.ID, r.Score, title, description, r.AdditionalProperties)
   443  		}
   444  	})
   445  
   446  	t.Run("Hybrid with negative limit", func(t *testing.T) {
   447  		params := dto.GetParams{
   448  			ClassName: "MyClass",
   449  			HybridSearch: &searchparams.HybridSearch{
   450  				Query:  "Elephant Parade",
   451  				Vector: elephantVector(),
   452  				Alpha:  0.5,
   453  			},
   454  			Pagination: &filters.Pagination{
   455  				Offset: 0,
   456  				Limit:  -1,
   457  			},
   458  		}
   459  
   460  		prov := modules.NewProvider()
   461  		prov.SetClassDefaults(class)
   462  
   463  		metrics := &fakeMetrics{}
   464  		log, _ := test.NewNullLogger()
   465  		explorer := traverser.NewExplorer(repo, log, prov, metrics, defaultConfig)
   466  		explorer.SetSchemaGetter(schemaGetter)
   467  		hybridResults, err := explorer.Hybrid(context.TODO(), params)
   468  
   469  		fmt.Println("--- Start results for hybrid with negative limit ---")
   470  		for _, r := range hybridResults {
   471  			schema := r.Schema.(map[string]interface{})
   472  			title := schema["title"].(string)
   473  			description := schema["description"].(string)
   474  			fmt.Printf("Result id: %v, score: %v, title: %v, description: %v, additional %+v\n", r.ID, r.Score, title, description, r.AdditionalProperties)
   475  		}
   476  		require.Nil(t, err)
   477  		require.True(t, len(hybridResults) > 0)
   478  	})
   479  
   480  	t.Run("Hybrid with offset", func(t *testing.T) {
   481  		params := dto.GetParams{
   482  			ClassName: "MyClass",
   483  			HybridSearch: &searchparams.HybridSearch{
   484  				Query:  "Elephant Parade",
   485  				Vector: elephantVector(),
   486  				Alpha:  0.5,
   487  			},
   488  			Pagination: &filters.Pagination{
   489  				Offset: 2,
   490  				Limit:  1,
   491  			},
   492  		}
   493  
   494  		prov := modules.NewProvider()
   495  		prov.SetClassDefaults(class)
   496  
   497  		metrics := &fakeMetrics{}
   498  		log, _ := test.NewNullLogger()
   499  		explorer := traverser.NewExplorer(repo, log, prov, metrics, defaultConfig)
   500  		explorer.SetSchemaGetter(schemaGetter)
   501  		hybridResults, err := explorer.Hybrid(context.TODO(), params)
   502  
   503  		fmt.Println("--- Start results for hybrid with offset 2 ---")
   504  		for _, r := range hybridResults {
   505  			schema := r.Schema.(map[string]interface{})
   506  			title := schema["title"].(string)
   507  			description := schema["description"].(string)
   508  			fmt.Printf("Result id: %v, score: %v, title: %v, description: %v, additional %+v\n", r.ID, r.Score, title, description, r.AdditionalProperties)
   509  		}
   510  
   511  		require.Nil(t, err)
   512  		require.True(t, len(hybridResults) == 1)
   513  		require.True(t, hybridResults[0].ID == "00000000-0000-0000-0000-000000000001")
   514  	})
   515  
   516  	t.Run("Hybrid with offset", func(t *testing.T) {
   517  		params := dto.GetParams{
   518  			ClassName: "MyClass",
   519  			HybridSearch: &searchparams.HybridSearch{
   520  				Query:  "Elephant Parade",
   521  				Vector: elephantVector(),
   522  				Alpha:  0.5,
   523  			},
   524  			Pagination: &filters.Pagination{
   525  				Offset: 4,
   526  				Limit:  1,
   527  			},
   528  		}
   529  
   530  		prov := modules.NewProvider()
   531  		prov.SetClassDefaults(class)
   532  
   533  		metrics := &fakeMetrics{}
   534  		log, _ := test.NewNullLogger()
   535  		explorer := traverser.NewExplorer(repo, log, prov, metrics, defaultConfig)
   536  		explorer.SetSchemaGetter(schemaGetter)
   537  		hybridResults, err := explorer.Hybrid(context.TODO(), params)
   538  
   539  		fmt.Println("--- Start results for hybrid with offset 4 ---")
   540  		for _, r := range hybridResults {
   541  			schema := r.Schema.(map[string]interface{})
   542  			title := schema["title"].(string)
   543  			description := schema["description"].(string)
   544  			fmt.Printf("Result id: %v, score: %v, title: %v, description: %v, additional %+v\n", r.ID, r.Score, title, description, r.AdditionalProperties)
   545  		}
   546  
   547  		require.Nil(t, err)
   548  		require.True(t, len(hybridResults) == 0)
   549  	})
   550  }
   551  
   552  func TestRFJourneyWithFilters(t *testing.T) {
   553  	dirName := t.TempDir()
   554  
   555  	logger := logrus.New()
   556  	schemaGetter := &fakeSchemaGetter{
   557  		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
   558  		shardState: singleShardState(),
   559  	}
   560  	repo, err := New(logger, Config{
   561  		RootPath:                  dirName,
   562  		QueryMaximumResults:       10000,
   563  		MaxImportGoroutinesFactor: 1,
   564  		QueryLimit:                20,
   565  	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, nil, nil)
   566  	require.Nil(t, err)
   567  	repo.SetSchemaGetter(schemaGetter)
   568  	require.Nil(t, repo.WaitForStartup(context.TODO()))
   569  	defer repo.Shutdown(context.Background())
   570  
   571  	class := SetupFusionClass(t, repo, schemaGetter, logger, 1.2, 0.75)
   572  	idx := repo.GetIndex("MyClass")
   573  	require.NotNil(t, idx)
   574  
   575  	filter := &filters.LocalFilter{
   576  		Root: &filters.Clause{
   577  			Operator: filters.OperatorOr,
   578  			Operands: []filters.Clause{
   579  				{
   580  					Operator: filters.OperatorEqual,
   581  					On: &filters.Path{
   582  						Class:    schema.ClassName("MyClass"),
   583  						Property: schema.PropertyName("title"),
   584  					},
   585  					Value: &filters.Value{
   586  						Value: "elephant",
   587  						Type:  schema.DataTypeText,
   588  					},
   589  				},
   590  				{
   591  					Operator: filters.OperatorEqual,
   592  					On: &filters.Path{
   593  						Class:    schema.ClassName("MyClass"),
   594  						Property: schema.PropertyName("title"),
   595  					},
   596  					Value: &filters.Value{
   597  						Value: "elephant",
   598  						Type:  schema.DataTypeText,
   599  					},
   600  				},
   601  			},
   602  		},
   603  	}
   604  
   605  	filter1 := &filters.LocalFilter{
   606  		Root: &filters.Clause{
   607  			Operator: filters.OperatorOr,
   608  			Operands: []filters.Clause{
   609  				{
   610  					Operator: filters.OperatorEqual,
   611  					On: &filters.Path{
   612  						Class:    schema.ClassName("MyClass"),
   613  						Property: schema.PropertyName("title"),
   614  					},
   615  					Value: &filters.Value{
   616  						Value: "My",
   617  						Type:  schema.DataTypeText,
   618  					},
   619  				},
   620  				{
   621  					Operator: filters.OperatorEqual,
   622  					On: &filters.Path{
   623  						Class:    schema.ClassName("MyClass"),
   624  						Property: schema.PropertyName("title"),
   625  					},
   626  					Value: &filters.Value{
   627  						Value: "journeys",
   628  						Type:  schema.DataTypeText,
   629  					},
   630  				},
   631  			},
   632  		},
   633  	}
   634  
   635  	t.Run("Hybrid with filter - no results expected", func(t *testing.T) {
   636  		params := dto.GetParams{
   637  			ClassName: "MyClass",
   638  			HybridSearch: &searchparams.HybridSearch{
   639  				Query:  "elephant",
   640  				Vector: elephantVector(),
   641  				Alpha:  0.5,
   642  			},
   643  			Pagination: &filters.Pagination{
   644  				Offset: 0,
   645  				Limit:  100,
   646  			},
   647  			Filters: filter1,
   648  		}
   649  
   650  		prov := modules.NewProvider()
   651  		prov.SetClassDefaults(class)
   652  
   653  		metrics := &fakeMetrics{}
   654  		log, _ := test.NewNullLogger()
   655  		explorer := traverser.NewExplorer(repo, log, prov, metrics, defaultConfig)
   656  		explorer.SetSchemaGetter(schemaGetter)
   657  		hybridResults, err := explorer.Hybrid(context.TODO(), params)
   658  		require.Nil(t, err)
   659  		require.Equal(t, 0, len(hybridResults))
   660  	})
   661  
   662  	t.Run("Hybrid", func(t *testing.T) {
   663  		params := dto.GetParams{
   664  			ClassName: "MyClass",
   665  			HybridSearch: &searchparams.HybridSearch{
   666  				Query:  "elephant",
   667  				Vector: elephantVector(),
   668  				Alpha:  0.5,
   669  			},
   670  			Pagination: &filters.Pagination{
   671  				Offset: 0,
   672  				Limit:  -1,
   673  			},
   674  		}
   675  
   676  		prov := modules.NewProvider()
   677  		prov.SetClassDefaults(class)
   678  
   679  		metrics := &fakeMetrics{}
   680  		log, _ := test.NewNullLogger()
   681  		explorer := traverser.NewExplorer(repo, log, prov, metrics, defaultConfig)
   682  		explorer.SetSchemaGetter(schemaGetter)
   683  		hybridResults, err := explorer.Hybrid(context.TODO(), params)
   684  		require.Nil(t, err)
   685  		require.Equal(t, 3, len(hybridResults))
   686  
   687  		fmt.Println("--- Start results for hybrid vector ---")
   688  		for _, r := range hybridResults {
   689  			schema := r.Schema.(map[string]interface{})
   690  			title := schema["title"].(string)
   691  			description := schema["description"].(string)
   692  			fmt.Printf("Result id: %v, score: %v, title: %v, description: %v, additional %+v\n", r.ID, r.Score, title, description, r.AdditionalProperties)
   693  		}
   694  		require.Equal(t, strfmt.UUID("00000000-0000-0000-0000-000000000002"), hybridResults[0].ID)
   695  	})
   696  
   697  	t.Run("Hybrid with filter", func(t *testing.T) {
   698  		params := dto.GetParams{
   699  			ClassName: "MyClass",
   700  			HybridSearch: &searchparams.HybridSearch{
   701  				Query:  "elephant",
   702  				Vector: elephantVector(),
   703  				Alpha:  0.5,
   704  			},
   705  			Pagination: &filters.Pagination{
   706  				Offset: 0,
   707  				Limit:  -1,
   708  			},
   709  			Filters: filter,
   710  		}
   711  
   712  		prov := modules.NewProvider()
   713  		prov.SetClassDefaults(class)
   714  
   715  		metrics := &fakeMetrics{}
   716  		log, _ := test.NewNullLogger()
   717  		explorer := traverser.NewExplorer(repo, log, prov, metrics, defaultConfig)
   718  		explorer.SetSchemaGetter(schemaGetter)
   719  		hybridResults, err := explorer.Hybrid(context.TODO(), params)
   720  		require.Nil(t, err)
   721  		require.Equal(t, 1, len(hybridResults))
   722  
   723  		fmt.Println("--- Start results for hybrid with filter---")
   724  		for _, r := range hybridResults {
   725  			schema := r.Schema.(map[string]interface{})
   726  			title := schema["title"].(string)
   727  			description := schema["description"].(string)
   728  			fmt.Printf("Result id: %v, score: %v, title: %v, description: %v, additional %+v\n", r.ID, r.Score, title, description, r.AdditionalProperties)
   729  		}
   730  		require.Equal(t, strfmt.UUID("00000000-0000-0000-0000-000000000002"), hybridResults[0].ID)
   731  	})
   732  }
   733  
   734  func TestStability(t *testing.T) {
   735  	dirName := t.TempDir()
   736  
   737  	logger := logrus.New()
   738  	schemaGetter := &fakeSchemaGetter{
   739  		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
   740  		shardState: singleShardState(),
   741  	}
   742  	repo, err := New(logger, Config{
   743  		RootPath:                  dirName,
   744  		QueryMaximumResults:       10000,
   745  		MaxImportGoroutinesFactor: 1,
   746  		QueryLimit:                20,
   747  	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, nil, nil)
   748  	require.Nil(t, err)
   749  	repo.SetSchemaGetter(schemaGetter)
   750  	require.Nil(t, repo.WaitForStartup(context.TODO()))
   751  	defer repo.Shutdown(context.Background())
   752  
   753  	SetupFusionClass(t, repo, schemaGetter, logger, 1.2, 0.75)
   754  	idx := repo.GetIndex("MyClass")
   755  	require.NotNil(t, idx)
   756  
   757  	docId1 := uint64(1)
   758  	docId2 := uint64(2)
   759  	docId3 := uint64(3)
   760  
   761  	doc1 := &search.Result{
   762  		ID:    strfmt.UUID("e6f7e8b1-ac53-48eb-b6e4-cbe67396bcfa"),
   763  		DocID: &docId1,
   764  		Schema: map[string]interface{}{
   765  			"title": "peanuts",
   766  		},
   767  		Vector: []float32{0.1, 0.2, 0.3, 0.4, 0.5},
   768  		Score:  0.1,
   769  	}
   770  
   771  	doc2 := &search.Result{
   772  		ID:    strfmt.UUID("e6f7e8b1-ac53-48eb-b6e4-cbe67396bcfb"),
   773  		DocID: &docId2,
   774  		Schema: map[string]interface{}{
   775  			"title": "peanuts",
   776  		},
   777  		Vector: []float32{0.1, 0.2, 0.3, 0.4, 0.5},
   778  		Score:  0.1,
   779  	}
   780  
   781  	doc3 := &search.Result{
   782  		ID:    strfmt.UUID("e6f7e8b1-ac53-48eb-b6e4-cbe67396bcfc"),
   783  		DocID: &docId3,
   784  		Schema: map[string]interface{}{
   785  			"title": "peanuts",
   786  		},
   787  		Vector: []float32{0.1, 0.2, 0.3, 0.4, 0.5},
   788  		Score:  0.1,
   789  	}
   790  
   791  	resultSet1 := []*search.Result{doc1, doc2, doc3}
   792  	resultSet2 := []*search.Result{doc2, doc1, doc3}
   793  
   794  	t.Run("Fusion Reciprocal", func(t *testing.T) {
   795  		results := hybrid.FusionRanked([]float64{0.4, 0.6},
   796  			[][]*search.Result{resultSet1, resultSet2}, []string{"set1", "set2"})
   797  		fmt.Println("--- Start results for Fusion Reciprocal ---")
   798  		for _, result := range results {
   799  			schema := result.Schema.(map[string]interface{})
   800  			fmt.Println(schema["title"], result.ID, result.Score)
   801  		}
   802  		require.Equal(t, 3, len(results))
   803  		require.Equal(t, resultSet2[0].ID, results[0].ID)
   804  		require.Equal(t, resultSet2[1].ID, results[1].ID)
   805  		require.Equal(t, resultSet2[2].ID, results[2].ID)
   806  	})
   807  }
   808  
   809  func elephantVector() []float32 {
   810  	return []float32{
   811  		-0.106136, -0.021716, 0.632442, 0.195315, -0.038854, -0.260533, -0.728847, -0.313725, -0.161967, 0.179243, -0.124185, 0.158839, 0.09563, -0.071267, 0.073928, -0.096735, 0.27266, -0.204127, -0.387028, -0.361406, -0.278027, 0.298766, 0.265405, 0.037477, -0.079904, -0.778953, -0.525643, -0.052346, -0.2174, 0.095746, 0.610937, 0.315672, -0.125526, 0.013475, -0.075578, -0.053183, -0.381475, 0.620278, -0.093857, 0.802608, -0.105773, -0.007902, 0.663528, 0.407708, 0.753832, 0.420718, 0.139289, -0.126864, 0.36345, -0.039222, 0.089002, 0.092151, 0.138025, 0.18881, 0.51416, -0.391045, -0.169528, -0.044023, 0.437196, -0.23917, 0.081247, -0.440846, -0.484764, 0.090495, 0.001852, -0.03441, 0.18548, -0.440182, 0.286827, -0.081451, 0.030155, -0.072746, -0.366531, 0.354118, 0.418432, -0.305682, 0.515893, -0.424999, -0.495273, 0.731375, 0.358407, -0.415989, 0.441337, -0.022167, 0.318837, -0.473018, 0.342046, -0.499794, -0.303161, -0.379234, -0.279082, -0.325648, 0.200613, -0.457396, 0.116745, 0.225836, -0.322175, -0.151425, 0.322014, 0.077097, 0.049998, -0.01005, 0.489028, -0.273297, 0.218896, -0.507729, 0.488891, -0.207774, -0.499136, 0.992803, 0.379556, -0.572352, -0.295821, -0.071392, -0.625823, -0.425159, 0.024593, 0.307965, 0.311686, 0.287844, 0.435028, 0.454474, -0.208158, -0.111947, -0.380334, -0.392014, 1.747561, 0.360315, 0.472088, 0.273835, 0.635424, 0.390057, -0.021349, -0.746944, 0.265353, 0.60709, -0.171053, 0.408823, -0.059646, 0.058306, -0.062817, -0.41064, -0.342016, -0.048077, 0.862758, -0.217101, -0.048961, -0.314094, 0.228395, -0.339353, 0.558551, 0.370054, -0.319855, 0.543137, 0.71334, 0.166296, 0.040412, -0.160482, 0.432088, -0.491292, 0.072819, -0.409627, 0.300197, 0.169077, 0.44379, 0.117131, 0.142459, -0.482226, -0.100245, 0.058273, -0.590567, -0.061971, -0.415718, -0.018105, -0.693528, -0.047609, -0.041873, 0.606186, 0.19767, -0.091001, -0.315381, -1.234111, 0.228805, -0.636861, 0.208757, -0.270024, -0.259684, -0.351592, -0.978549, 0.683986, -0.331669, -0.078729, 0.385676, 0.390955, -0.901898, -0.071451, -0.103991, 0.206379, 0.469656, 0.071528, -0.152589, 0.282268, 0.539651, -0.856463, -0.344053, -0.40572, 0.771483, -0.065611, -0.408832, 0.303948, -0.565157, 0.153293, -0.699892, 1.112725, 0.259508, 0.135771, 0.484552, 0.151274, -0.743235, 0.069811, 0.137583, 0.212661, 0.376839, 0.136164, 0.145626, -0.466645, -0.474334, -0.365033, 0.251158, -0.313904, 0.210487, -1.016155, 0.262768, 0.432895, -0.291339, -0.221825, 0.513278, 0.659038, -0.401398, -0.164522, 0.395279, -0.449811, 0.076142, 0.389243, 0.076184, 0.05539, -0.597094, -0.149824, 0.206724, -0.477001, -0.315719, 0.166689, -0.357187, 0.34429, 0.256624, -0.236781, -0.713059, -0.440255, 0.27353, -0.032257, 0.06925, 0.359134, -0.088975, 0.112507, -0.071103, 0.880417, 0.528587, 0.155656, -0.720531, 0.3068, 0.754715, 0.009366, 0.067487, -0.11898, -0.471064, -0.396507, 0.298669, 0.038283, 0.057218, -0.075818, -0.01513, -0.319236, 0.692123, -0.122985, 0.875938, 0.378184, 0.427029, 0.315545, -0.549573, 0.389602, -0.017071, 0.160122, 0.368208, 0.060474, -0.199651, 0.087829, 0.447339, 0.012265, -0.095388, -0.07034,
   812  	}
   813  }
   814  
   815  // "journey"
   816  func journeyVector() []float32 {
   817  	return []float32{
   818  		-0.523002, 0.14169, 0.016461, -0.069062, 0.487908, -0.024193, -0.282436, 0.004778, -0.378135, 0.396011, 0.094045, -0.06584, 0.061162, -0.600018, -0.110189, 0.244562, 0.433501, 0.303775, -0.451004, -0.453709, 0.350324, 0.2047, -0.091615, -0.282805, -0.232953, -0.215143, 0.333113, -0.126952, -0.639225, 0.101498, 0.232343, 0.58831, 0.971, 0.494446, -0.483305, -0.873438, -0.483694, 0.406465, 0.342816, 1.253387, -0.24718, -0.046063, -0.660406, 0.103386, -0.06063, 0.3422, 0.322542, 0.026074, -0.623612, 0.489793, -0.632363, 0.448922, -0.370049, 0.212377, -0.315855, 0.364525, 0.056798, 0.805679, 0.145633, 0.850648, 0.432728, -1.431841, -0.226569, -0.315194, 0.560742, 0.261859, -0.001653, -0.068738, -0.662729, -0.049259, -0.380322, -0.374194, 0.363328, 0.341796, -0.077566, 0.503337, 0.353664, -0.045754, -0.499081, 0.198603, 0.038837, -0.460198, 0.00735, -0.270993, 0.950923, -0.085815, -0.52167, -0.10439, 0.31398, -0.560229, 0.411738, -0.129033, -0.009998, 0.443882, -0.045643, -0.078445, -0.259311, -0.08337, 0.232652, -0.015912, -0.229458, -0.474973, 1.265934, -0.204483, -0.293586, -0.619023, 0.158895, -0.730671, -0.163626, 0.411716, -0.000132, 0.069014, -0.682714, 0.303234, 0.299097, -0.484469, 0.608172, -0.163785, -0.419754, -0.160745, 0.278904, 0.550542, -0.008052, 0.160397, -0.211354, -0.19755, 1.182627, 0.705073, -0.461941, -0.235292, 0.534275, -0.096419, -0.405812, -0.157745, -0.335469, 0.200545, 0.406497, -0.05341, -0.009234, -0.029925, -0.394101, -0.060133, 0.182601, 0.615583, 0.212157, 0.363921, 0.41868, -0.652791, 0.657173, -0.131662, 0.269305, 0.381748, -0.827964, -0.452596, 0.201918, 0.0673, -0.020293, 0.486942, -0.72454, -0.435051, -0.615452, -0.218852, 0.090703, -0.471036, 0.032373, 0.569953, 0.098359, -0.570767, -0.21015, -0.53019, -0.227117, 0.327978, 0.087079, -0.115037, 0.09193, -0.922884, -0.165566, -0.353596, 0.535904, -0.328579, 0.029465, -1.508702, -0.320394, -0.596324, 0.290277, -0.272515, 0.104348, 0.062855, -0.236447, 0.388958, -0.186552, -0.156253, 0.355678, 0.53834, -0.321627, 0.486004, 0.301326, 0.786779, 0.430292, -0.012458, -0.164964, -0.072951, 0.746564, 0.19136, 0.003213, 0.53479, 0.511118, -0.559153, -0.088731, -0.436206, 0.421004, 0.193043, -0.656222, 0.133223, 0.00107, 0.037087, 0.263503, 0.378593, 0.158718, -0.401664, -0.10563, -0.111221, 0.018598, -0.036396, 0.189584, -0.347721, -0.544111, -0.018158, 0.134147, -0.362431, -0.702383, -0.375221, 0.365745, 0.118082, -0.19102, -0.150732, 0.638995, 0.070662, -0.054605, 0.221755, 0.23726, -0.274418, 0.294639, 0.221177, -0.012947, 0.08444, -0.486605, -0.225034, 0.774728, 0.167609, 0.766647, 0.381622, 0.241907, -0.196452, 0.245138, -0.203225, -0.701671, 0.236662, -0.627221, 0.143006, 0.055671, 0.564561, -0.114897, -0.542244, 0.464601, 0.201577, -0.177196, -0.795015, -0.580793, -0.134996, -0.579672, -0.399042, 0.008118, -0.458077, -0.43296, 0.074138, 0.328092, 0.02934, 0.406294, 0.330677, -0.138583, -0.676608, -0.099983, -0.137182, 0.713108, 0.248643, 0.153462, 0.56039, -0.109877, 0.260655, -0.529779, -0.13416, 0.067448, -0.139468, -0.179535, 0.372629, 0.287185, 0.100582, 0.093573, -0.208796,
   819  	}
   820  }
   821  
   822  // "peanuts"
   823  func peanutsVector() []float32 {
   824  	return []float32{0.563772, -0.779601, -0.18491, 0.509093, 0.080691, -0.621506, -0.127855, -0.165435, 0.57496, 0.006945, 0.452967, -0.285534, -0.129205, 0.193883, 0.092732, 0.083284, 0.714696, 0.107078, -0.398886, -0.117344, -0.387671, 0.026748, -0.562581, -0.007178, -0.354846, -0.431299, -0.788228, 0.175199, 0.914486, 0.441425, 0.089804, 0.284472, 0.106916, -0.133174, 0.399299, 0.002177, 0.551474, 0.389343, -0.016404, 0.770212, -0.219833, 0.303322, 0.127598, -0.378037, -0.172971, 0.394854, -0.424415, -0.71173, 0.080323, -0.406372, 0.398395, -0.594257, -0.418287, 0.055755, -0.352343, -0.393373, -0.732443, 0.333113, 0.420378, -0.50231, 0.261863, -0.061356, -0.180985, 0.311916, -0.180207, -0.154169, 0.371969, 0.454717, 0.320499, -0.182448, 0.087347, 0.585272, 0.136098, 0.288909, -0.229571, -0.140278, 0.229644, -0.557327, -0.110147, 0.034364, -0.021627, -0.598707, 0.221168, -0.059591, -0.203555, -0.434876, 0.209634, -0.460895, -0.345391, -0.18248, -0.24853, 0.730295, -0.295402, -0.562237, 0.255922, 0.076661, -0.713794, -0.354747, -1.109888, -0.066694, -0.195747, -0.282781, 0.459869, -0.309599, -0.002211, -0.274471, -0.003621, 0.008228, 0.011961, -0.258772, -0.210687, -0.664148, -0.257968, 0.231335, 0.530392, -0.205764, -0.621055, -0.440582, 0.080335, 0.017367, 0.880771, 0.656272, -0.713248, -0.208629, 0.095346, 0.336802, 0.888765, 0.251927, 0.066473, 0.182678, -0.220494, 0.288927, -0.602036, 0.057106, -0.594172, 0.848978, 0.751973, 0.090758, -0.732184, 0.683475, -0.075085, 0.381326, -0.076531, -0.253831, 0.10311, -0.02988, -0.043583, 0.005746, -0.460183, -0.189048, 0.25792, 0.477565, 0.391953, 0.08469, -0.10022, 0.454383, 0.170811, 0.196819, -0.760276, 0.045886, -0.743934, 0.190072, -0.216326, -0.624262, -0.22944, 0.066233, 1.024283, 0.044009, -0.373543, -0.243663, 0.204444, 0.402183, 0.043356, 0.31716, 0.302178, 0.369374, 0.36901, 0.02886, -0.26132, -0.234714, -0.791308, -0.433528, -0.098797, -0.447567, -0.124892, -0.119958, 0.31019, -0.096092, -0.259021, -0.078099, -0.178679, 0.14879, 0.106432, -0.450003, -0.294972, 0.044257, 0.402832, 0.263266, -0.309787, -0.17766, -0.399104, 0.577422, 0.30102, 0.05326, -0.271873, 0.204839, -0.019002, -0.743543, 0.739314, -0.115868, -0.504568, -0.115713, 0.042769, -0.123561, -0.057097, 0.407096, 0.770627, 0.372981, -0.321945, 0.349865, 0.437571, -0.77394, -0.090017, -0.011273, -0.468664, -0.735247, -0.745655, 0.018983, -0.248165, 0.215342, -0.136942, -0.458205, 0.4572, -0.032293, 0.654409, -0.024184, -0.392144, 0.634579, 0.222185, 0.471951, -0.063678, -0.473611, 0.796793, -0.295494, -0.157621, -0.103365, -0.564606, -0.092231, -0.517754, -0.369358, 0.137479, -0.214837, 0.11057, -0.095227, 0.726768, -0.079352, -0.065927, -0.846602, -0.317556, -0.344271, 0.201353, -0.367633, -0.004477, 0.157801, -0.249114, -0.549599, -0.147123, 0.308084, -0.175564, 0.306867, -0.071157, -0.588356, 0.450987, -0.184879, -0.096782, -0.006346, -0.017689, 0.005998, 0.200963, 0.225338, 0.189993, -1.105824, 0.520005, 0.129679, 0.198194, -0.254813, -0.127583, 0.326054, 0.009956, -0.016008, -0.483044, 0.801135, -0.517766, 0.067179, -0.372756, -0.511781, 0.058562, -0.082906, -0.28168, -0.285859}
   825  }
   826  
   827  type fakeMetrics struct {
   828  	mock.Mock
   829  }
   830  
   831  func (m *fakeMetrics) AddUsageDimensions(class, query, op string, dims int) {
   832  	m.Called(class, query, op, dims)
   833  }
   834  
   835  type fakeObjectSearcher struct{}
   836  
   837  func (f *fakeObjectSearcher) Search(context.Context, dto.GetParams) ([]search.Result, error) {
   838  	return nil, nil
   839  }
   840  
   841  func (f *fakeObjectSearcher) VectorSearch(context.Context, dto.GetParams) ([]search.Result, error) {
   842  	return nil, nil
   843  }
   844  
   845  func (f *fakeObjectSearcher) CrossClassVectorSearch(context.Context, []float32, string, int, int, *filters.LocalFilter) ([]search.Result, error) {
   846  	return nil, nil
   847  }
   848  
   849  func (f *fakeObjectSearcher) Object(ctx context.Context, className string, id strfmt.UUID, props search.SelectProperties, additional additional.Properties, properties *additional.ReplicationProperties, tenant string) (*search.Result, error) {
   850  	return nil, nil
   851  }
   852  
   853  func (f *fakeObjectSearcher) ObjectsByID(ctx context.Context, id strfmt.UUID, props search.SelectProperties, additional additional.Properties, tenant string) (search.Results, error) {
   854  	return nil, nil
   855  }
   856  
   857  func (f *fakeObjectSearcher) SparseObjectSearch(ctx context.Context, params dto.GetParams) ([]*storobj.Object, []float32, error) {
   858  	out := []*storobj.Object{
   859  		{
   860  			Object: models.Object{
   861  				ID: "9889a225-3b28-477d-b8fc-5f6071bb4731",
   862  			},
   863  
   864  			Vector: []float32{1, 2, 3},
   865  		},
   866  		{
   867  			Object: models.Object{
   868  				ID: "0bcdef12-3314-442e-a4d1-e94d7c0afc3a",
   869  			},
   870  			Vector: []float32{4, 5, 6},
   871  		},
   872  	}
   873  	lim := params.Pagination.Offset + params.Pagination.Limit
   874  	if lim > len(out) {
   875  		lim = len(out)
   876  	}
   877  
   878  	return out[:lim], []float32{0.008, 0.001}[:lim], nil
   879  }
   880  
   881  func (f *fakeObjectSearcher) DenseObjectSearch(ctx context.Context, class string, vector []float32, targetVector string, offset int, limit int, filters *filters.LocalFilter, additinal additional.Properties, tenant string) ([]*storobj.Object, []float32, error) {
   882  	out := []*storobj.Object{
   883  		{
   884  			Object: models.Object{
   885  				ID: "79a636c2-3314-442e-a4d1-e94d7c0afc3a",
   886  			},
   887  
   888  			Vector: []float32{4, 5, 6},
   889  		},
   890  		{
   891  			Object: models.Object{
   892  				ID: "9889a225-3b28-477d-b8fc-5f6071bb4731",
   893  			},
   894  
   895  			Vector: []float32{1, 2, 3},
   896  		},
   897  	}
   898  	lim := offset + limit
   899  	if lim > len(out) {
   900  		lim = len(out)
   901  	}
   902  
   903  	return out[:lim], []float32{0.009, 0.008}[:lim], nil
   904  }
   905  
   906  func CopyElems[T any](list1, list2 []T, pos int) bool {
   907  	if len(list1) != len(list2) {
   908  		return false
   909  	}
   910  	if pos < 0 || pos >= len(list1) {
   911  		return true
   912  	}
   913  	list1[pos] = list2[pos]
   914  	return CopyElems(list1, list2, pos+1)
   915  }
   916  
   917  func (f *fakeObjectSearcher) ResolveReferences(ctx context.Context, objs search.Results, props search.SelectProperties, groupBy *searchparams.GroupBy, additional additional.Properties, tenant string) (search.Results, error) {
   918  	// Convert res1 to search.Results
   919  	out := make(search.Results, len(objs))
   920  	CopyElems(out, objs, 0)
   921  
   922  	return out, nil
   923  }
   924  
   925  func TestHybridOverSearch(t *testing.T) {
   926  	dirName := t.TempDir()
   927  
   928  	logger := logrus.New()
   929  	schemaGetter := &fakeSchemaGetter{
   930  		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
   931  		shardState: singleShardState(),
   932  	}
   933  	repo, err := New(logger, Config{
   934  		RootPath:                  dirName,
   935  		QueryMaximumResults:       10000,
   936  		MaxImportGoroutinesFactor: 1,
   937  		QueryLimit:                20,
   938  	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, nil, nil)
   939  	require.Nil(t, err)
   940  	repo.SetSchemaGetter(schemaGetter)
   941  	require.Nil(t, repo.WaitForStartup(context.TODO()))
   942  	defer repo.Shutdown(context.Background())
   943  
   944  	fos := &fakeObjectSearcher{}
   945  
   946  	class := SetupFusionClass(t, repo, schemaGetter, logger, 1.2, 0.75)
   947  	idx := repo.GetIndex("MyClass")
   948  	require.NotNil(t, idx)
   949  
   950  	t.Run("Hybrid", func(t *testing.T) {
   951  		params := dto.GetParams{
   952  			ClassName: "MyClass",
   953  			HybridSearch: &searchparams.HybridSearch{
   954  				Query:  "elephant",
   955  				Vector: elephantVector(),
   956  				Alpha:  0.5,
   957  			},
   958  			Pagination: &filters.Pagination{
   959  				Offset: 0,
   960  				Limit:  1,
   961  			},
   962  		}
   963  
   964  		prov := modules.NewProvider()
   965  		prov.SetClassDefaults(class)
   966  
   967  		metrics := &fakeMetrics{}
   968  		log, _ := test.NewNullLogger()
   969  		explorer := traverser.NewExplorer(fos, log, prov, metrics, defaultConfig)
   970  		explorer.SetSchemaGetter(schemaGetter)
   971  		hybridResults, err := explorer.Hybrid(context.TODO(), params)
   972  		require.Nil(t, err)
   973  		require.Equal(t, 1, len(hybridResults))
   974  		require.Equal(t, strfmt.UUID("9889a225-3b28-477d-b8fc-5f6071bb4731"), hybridResults[0].ID)
   975  		// require.Equal(t, "79a636c2-3314-442e-a4d1-e94d7c0afc3a", hybridResults[1].ID)
   976  	})
   977  }