├── project ├── build.properties └── plugins.sbt ├── src ├── main │ ├── resources │ │ └── application.conf │ └── scala │ │ └── digital │ │ └── ivan │ │ └── vecoluc │ │ ├── model │ │ ├── ItemDoc.scala │ │ └── ApiModel.scala │ │ ├── reader │ │ ├── IndexSearchService.scala │ │ └── LuceneSearcher.scala │ │ ├── writer │ │ └── LuceneWriterService.scala │ │ ├── VecolucApp.scala │ │ └── routes │ │ ├── SearchRoutes.scala │ │ └── IndexRoutes.scala └── test │ └── scala │ └── digital │ └── ivan │ └── vecoluc │ ├── reader │ └── LuceneSearcherTest.scala │ └── writer │ └── LuceneWriterServiceTest.scala ├── .gitignore └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.10.1 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.2") 2 | -------------------------------------------------------------------------------- /src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka.http { 2 | server { 3 | parsing.max-content-length = 100m 4 | } 5 | } -------------------------------------------------------------------------------- /src/main/scala/digital/ivan/vecoluc/model/ItemDoc.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc.model 2 | 3 | case class ItemDoc(fields: Map[String, String], score: Float) 4 | -------------------------------------------------------------------------------- /src/main/scala/digital/ivan/vecoluc/model/ApiModel.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc.model 2 | 3 | case class Document(metadata: Map[String, String], embeddings: Array[Float]) 4 | 5 | case class Response[T](status: String, latencyMillis: Long, data: Option[T]) 6 | 7 | case class SearchResults(results: Seq[ItemDoc], count: Int) 8 | 9 | case class SearchRequest(query: Array[Float], topN: Int) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !**/src/main/**/target/ 3 | !**/src/test/**/target/ 4 | 5 | .idea/modules.xml 6 | .idea/jarRepositories.xml 7 | .idea/compiler.xml 8 | .idea/libraries/ 9 | *.iws 10 | *.iml 11 | *.ipr 12 | 13 | ### Eclipse ### 14 | .apt_generated 15 | .classpath 16 | .factorypath 17 | .project 18 | .settings 19 | .springBeans 20 | .sts4-cache 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | ### Mac OS ### 36 | .DS_Store 37 | /.bsp/ 38 | /.idea/ 39 | /index/ 40 | /project/project/ 41 | -------------------------------------------------------------------------------- /src/main/scala/digital/ivan/vecoluc/reader/IndexSearchService.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc.reader 2 | 3 | import digital.ivan.vecoluc.model.ItemDoc 4 | import org.apache.lucene.search.KnnFloatVectorQuery 5 | 6 | class IndexSearchService(searchService: LuceneSearcher) { 7 | 8 | def searchByVector(vector: Array[Float], topN: Int): Seq[ItemDoc] = { 9 | val query = new KnnFloatVectorQuery("embeddings", vector, topN) 10 | val scoreDocs = searchService.search(query, topN) 11 | 12 | scoreDocs 13 | .map { scoreDoc => 14 | documentToItemDoc(searchService.getDocument(scoreDoc.doc), scoreDoc.score) 15 | }.toSeq 16 | } 17 | 18 | private def documentToItemDoc(fields: Map[String, String], score: Float): ItemDoc = { 19 | ItemDoc(fields, score) 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/scala/digital/ivan/vecoluc/writer/LuceneWriterService.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc.writer 2 | 3 | import org.apache.lucene.document.{Document, KnnFloatVectorField, StoredField} 4 | import org.apache.lucene.index.{IndexWriter, IndexWriterConfig} 5 | import org.apache.lucene.store.MMapDirectory 6 | 7 | import java.nio.file.Paths 8 | 9 | class LuceneWriterService(indexWriter: IndexWriter) { 10 | 11 | def this(indexPath: String) = { 12 | this(new IndexWriter(new MMapDirectory(Paths.get(indexPath)), new IndexWriterConfig())) 13 | indexWriter.commit() 14 | } 15 | 16 | def addDocuments(documents: Seq[(Map[String, String], Array[Float])]): Unit = { 17 | documents.foreach { case (metadata, embeddings) => 18 | addDocument(metadata, embeddings) 19 | } 20 | } 21 | 22 | def addDocument(metadata: Map[String, String], embeddings: Array[Float]): Unit = { 23 | val doc = new Document() 24 | 25 | metadata.foreach { case (name, value) => 26 | doc.add(new StoredField(name, value)) 27 | } 28 | 29 | doc.add(new KnnFloatVectorField("embeddings", embeddings)) 30 | 31 | indexWriter.addDocument(doc) 32 | } 33 | 34 | def commit(): Unit = { 35 | indexWriter.commit() 36 | } 37 | 38 | def close(): Unit = { 39 | indexWriter.close() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/scala/digital/ivan/vecoluc/reader/LuceneSearcher.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc.reader 2 | 3 | import org.apache.lucene.index.DirectoryReader 4 | import org.apache.lucene.search.{IndexSearcher, KnnFloatVectorQuery, ScoreDoc} 5 | import org.apache.lucene.store.FSDirectory 6 | 7 | import java.lang.Runtime.getRuntime 8 | import java.nio.file.Paths 9 | import java.util.concurrent.ExecutorService 10 | import java.util.concurrent.Executors.newFixedThreadPool 11 | import scala.jdk.CollectionConverters.CollectionHasAsScala 12 | 13 | class LuceneSearcher(indexSearcher: IndexSearcher) { 14 | 15 | def search(query: KnnFloatVectorQuery, topN: Int): Array[ScoreDoc] = { 16 | indexSearcher.search(query, topN).scoreDocs 17 | } 18 | 19 | def getDocument(docId: Int): Map[String, String] = { 20 | val document = indexSearcher.getIndexReader.document(docId) 21 | document.getFields.asScala.map { field => 22 | field.name() -> document.get(field.name()) 23 | }.toMap 24 | } 25 | } 26 | 27 | object LuceneSearcher { 28 | def apply(indexPath: String): LuceneSearcher = { 29 | val indexDirectory = FSDirectory.open(Paths.get(indexPath)) 30 | val reader = DirectoryReader.open(indexDirectory) 31 | val threadPool: ExecutorService = newFixedThreadPool(getRuntime.availableProcessors()) 32 | val indexSearcher = new IndexSearcher(reader, threadPool) 33 | new LuceneSearcher(indexSearcher) 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/scala/digital/ivan/vecoluc/VecolucApp.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.server.Directives._ 6 | import com.typesafe.scalalogging.LazyLogging 7 | import digital.ivan.vecoluc.reader.{IndexSearchService, LuceneSearcher} 8 | import digital.ivan.vecoluc.routes.{IndexRoutes, SearchRoutes} 9 | import digital.ivan.vecoluc.writer.LuceneWriterService 10 | 11 | import scala.io.StdIn.readLine 12 | import scala.util.{Failure, Success} 13 | 14 | object VecolucApp extends App with LazyLogging { 15 | 16 | private val host = "0.0.0.0" 17 | private val port = 9000 18 | private val indexName = "index" 19 | 20 | implicit val system: ActorSystem = ActorSystem(name = "vecoluc") 21 | 22 | import system.dispatcher 23 | 24 | private val luceneWriteService = new LuceneWriterService(indexName) 25 | private val luceneIndexSearcher = LuceneSearcher(indexName) 26 | private val searchService = new IndexSearchService(luceneIndexSearcher) 27 | private val indexRoutes = new IndexRoutes(luceneWriteService) 28 | private val searchRoutes = new SearchRoutes(searchService) 29 | private val binding = Http().newServerAt(host, port).bind(route) 30 | 31 | private def route = concat( 32 | indexRoutes.routes, 33 | searchRoutes.routes 34 | ) 35 | binding.onComplete { 36 | case Success(_) => 37 | logger.info("VecoLuc: Listening for incoming connections!") 38 | case Failure(error) => 39 | logger.error("VecoLuc failed to start", error) 40 | } 41 | 42 | sys.addShutdownHook { 43 | try { 44 | luceneWriteService.close() 45 | logger.info("Lucene writer closed successfully.") 46 | } catch { 47 | case ex: Exception => 48 | logger.error("Failed to close Lucene writer", ex) 49 | } 50 | } 51 | 52 | readLine() 53 | } 54 | -------------------------------------------------------------------------------- /src/main/scala/digital/ivan/vecoluc/routes/SearchRoutes.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc.routes 2 | 3 | import akka.http.scaladsl.model.StatusCodes 4 | import akka.http.scaladsl.server.Directives._ 5 | import akka.http.scaladsl.server.Route 6 | import com.typesafe.scalalogging.LazyLogging 7 | import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ 8 | import digital.ivan.vecoluc.model.{ItemDoc, Response, SearchRequest, SearchResults} 9 | import digital.ivan.vecoluc.reader.IndexSearchService 10 | import io.circe.generic.auto._ 11 | 12 | import scala.concurrent.duration._ 13 | import scala.concurrent.{ExecutionContext, Future, blocking} 14 | import scala.util.{Failure, Success} 15 | 16 | class SearchRoutes(searchService: IndexSearchService)(implicit ec: ExecutionContext) extends LazyLogging { 17 | 18 | def routes: Route = 19 | path("search") { 20 | post { 21 | extractRequestContext { _ => 22 | val startTime = System.nanoTime() 23 | try { 24 | entity(as[SearchRequest]) { searchRequest => 25 | val searchFuture: Future[Seq[ItemDoc]] = Future { 26 | blocking { 27 | searchService.searchByVector(searchRequest.query, searchRequest.topN) 28 | } 29 | } 30 | 31 | onComplete(searchFuture) { 32 | case Success(results) => 33 | val latency = (System.nanoTime() - startTime).nanos.toMillis 34 | val searchResults = SearchResults(results, results.length) 35 | complete(StatusCodes.OK, Response("success", latency, Some(searchResults))) 36 | case Failure(ex) => 37 | logger.error("Failed to execute search", ex) 38 | complete(StatusCodes.InternalServerError, Response("error", 0, Some(ex.getMessage))) 39 | } 40 | } 41 | } catch { 42 | case ex: Exception => 43 | logger.error("Failed to execute search", ex) 44 | complete(StatusCodes.InternalServerError, Response("error", 0, Some(ex.getMessage))) 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/scala/digital/ivan/vecoluc/reader/LuceneSearcherTest.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc.reader 2 | 3 | import org.apache.lucene.index.IndexReader 4 | import org.apache.lucene.search.{IndexSearcher, KnnFloatVectorQuery, ScoreDoc, TopDocs, TotalHits} 5 | import org.mockito.ArgumentMatchers.any 6 | import org.mockito.Mockito._ 7 | import org.scalatest.flatspec.AnyFlatSpec 8 | import org.scalatest.matchers.must.Matchers 9 | import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper 10 | import org.scalatestplus.mockito.MockitoSugar 11 | 12 | class LuceneSearcherTest extends AnyFlatSpec with Matchers with MockitoSugar { 13 | 14 | "search" should "return a sequence of ScoreDocs when indexSearcher is present" in { 15 | val indexSearcher = mock[IndexSearcher] 16 | val indexReader = mock[IndexReader] 17 | when(indexSearcher.getIndexReader).thenReturn(indexReader) 18 | 19 | val luceneSearcher = new LuceneSearcher(indexSearcher) 20 | 21 | val scoreDocs = Array(new ScoreDoc(1, 1.0f), new ScoreDoc(2, 0.9f)) 22 | val topDocs = new TopDocs(new TotalHits(2, TotalHits.Relation.EQUAL_TO), scoreDocs) 23 | 24 | when(indexSearcher.search(any[KnnFloatVectorQuery], any[Int])).thenReturn(topDocs) 25 | 26 | val result = luceneSearcher.search(new KnnFloatVectorQuery("embeddings", Array(0.1f, 0.2f, 0.3f), 2), 2) 27 | 28 | result should have size 2 29 | result.head.doc should be(1) 30 | result.head.score should be(1.0f) 31 | result(1).doc should be(2) 32 | result(1).score should be(0.9f) 33 | } 34 | 35 | "getDocument" should "return a map of field names to values when a document is retrieved" in { 36 | val indexSearcher = mock[IndexSearcher] 37 | val indexReader = mock[IndexReader] 38 | when(indexSearcher.getIndexReader).thenReturn(indexReader) 39 | 40 | val luceneSearcher = new LuceneSearcher(indexSearcher) 41 | 42 | val doc = new org.apache.lucene.document.Document() 43 | doc.add(new org.apache.lucene.document.TextField("field1", "value1", org.apache.lucene.document.Field.Store.YES)) 44 | doc.add(new org.apache.lucene.document.TextField("field2", "value2", org.apache.lucene.document.Field.Store.YES)) 45 | 46 | when(indexReader.document(1)).thenReturn(doc) 47 | 48 | val result = luceneSearcher.getDocument(1) 49 | 50 | result should contain("field1" -> "value1") 51 | result should contain("field2" -> "value2") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/scala/digital/ivan/vecoluc/routes/IndexRoutes.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc.routes 2 | 3 | import akka.http.scaladsl.model.StatusCodes 4 | import akka.http.scaladsl.server.Directives._ 5 | import akka.http.scaladsl.server.Route 6 | import com.typesafe.scalalogging.LazyLogging 7 | import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ 8 | import digital.ivan.vecoluc.model.{Document, Response} 9 | import digital.ivan.vecoluc.writer.LuceneWriterService 10 | import io.circe.generic.auto._ 11 | 12 | import scala.concurrent.duration._ 13 | 14 | class IndexRoutes(luceneWriteService: LuceneWriterService) extends LazyLogging { 15 | 16 | def routes: Route = 17 | pathPrefix("index") { 18 | concat( 19 | path("docs") { 20 | post { 21 | extractRequestContext { _ => 22 | val startTime = System.nanoTime() 23 | try { 24 | entity(as[Seq[Document]]) { documents => 25 | luceneWriteService.addDocuments(documents.map(doc => (doc.metadata, doc.embeddings))) 26 | val latency = (System.nanoTime() - startTime).nanos.toMillis 27 | complete(StatusCodes.OK, Response("success", latency, Option.empty[Unit])) 28 | } 29 | } catch { 30 | case ex: Exception => 31 | logger.error("Failed to index documents", ex) 32 | val latency = (System.nanoTime() - startTime).nanos.toMillis 33 | complete(StatusCodes.InternalServerError, Response("error", latency, Some(ex.getMessage))) 34 | } 35 | } 36 | } 37 | }, 38 | path("commit") { 39 | post { 40 | extractRequestContext { _ => 41 | val startTime = System.nanoTime() 42 | try { 43 | luceneWriteService.commit() 44 | val latency = (System.nanoTime() - startTime).nanos.toMillis 45 | complete(StatusCodes.OK, Response("success", latency, Option.empty[Unit])) 46 | } catch { 47 | case ex: Exception => 48 | logger.error("Failed to commit changes", ex) 49 | val latency = (System.nanoTime() - startTime).nanos.toMillis 50 | complete(StatusCodes.InternalServerError, Response("error", latency, Some(ex.getMessage))) 51 | } 52 | } 53 | } 54 | } 55 | ) 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/test/scala/digital/ivan/vecoluc/writer/LuceneWriterServiceTest.scala: -------------------------------------------------------------------------------- 1 | package digital.ivan.vecoluc.reader 2 | 3 | import digital.ivan.vecoluc.writer.LuceneWriterService 4 | import org.apache.lucene.document.{Document, KnnFloatVectorField, StoredField} 5 | import org.apache.lucene.index.IndexWriter 6 | import org.mockito.ArgumentCaptor 7 | import org.mockito.Mockito._ 8 | import org.scalatest.flatspec.AnyFlatSpec 9 | import org.scalatest.matchers.must.Matchers 10 | import org.scalatestplus.mockito.MockitoSugar 11 | 12 | class LuceneWriterServiceTest extends AnyFlatSpec with Matchers with MockitoSugar { 13 | 14 | "addDocument" should "add a document with the correct metadata and embeddings" in { 15 | val indexWriter = mock[IndexWriter] 16 | val luceneWriterService = new LuceneWriterService(indexWriter) 17 | 18 | val metadata = Map("field1" -> "value1", "field2" -> "value2") 19 | val embeddings = Array(0.1f, 0.2f, 0.3f) 20 | 21 | luceneWriterService.addDocument(metadata, embeddings) 22 | 23 | val documentCaptor = ArgumentCaptor.forClass(classOf[Document]) 24 | verify(indexWriter).addDocument(documentCaptor.capture()) 25 | 26 | val capturedDoc = documentCaptor.getValue 27 | 28 | capturedDoc.get("field1") mustBe "value1" 29 | capturedDoc.get("field2") mustBe "value2" 30 | 31 | val embeddingField = capturedDoc.getField("embeddings").asInstanceOf[KnnFloatVectorField] 32 | embeddingField.name() mustBe "embeddings" 33 | } 34 | 35 | "addDocuments" should "add multiple documents" in { 36 | val indexWriter = mock[IndexWriter] 37 | val luceneWriterService = new LuceneWriterService(indexWriter) 38 | 39 | val documents = Seq( 40 | (Map("field1" -> "value1"), Array(0.1f, 0.2f)), 41 | (Map("field2" -> "value2"), Array(0.3f, 0.4f)) 42 | ) 43 | 44 | luceneWriterService.addDocuments(documents) 45 | 46 | val documentCaptor = ArgumentCaptor.forClass(classOf[Document]) 47 | verify(indexWriter, times(2)).addDocument(documentCaptor.capture()) 48 | 49 | val capturedDocs = documentCaptor.getAllValues 50 | 51 | capturedDocs.get(0).get("field1") mustBe "value1" 52 | val firstEmbeddingField = capturedDocs.get(0).getField("embeddings").asInstanceOf[KnnFloatVectorField] 53 | firstEmbeddingField.name() mustBe "embeddings" 54 | 55 | capturedDocs.get(1).get("field2") mustBe "value2" 56 | val secondEmbeddingField = capturedDocs.get(1).getField("embeddings").asInstanceOf[KnnFloatVectorField] 57 | secondEmbeddingField.name() mustBe "embeddings" 58 | } 59 | 60 | "close" should "close the IndexWriter" in { 61 | val indexWriter = mock[IndexWriter] 62 | val luceneWriterService = new LuceneWriterService(indexWriter) 63 | 64 | luceneWriterService.close() 65 | 66 | verify(indexWriter).close() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VecoLuc: Vector Search Engine 2 | 3 | ## Abstract 4 | 5 | VecoLuc is a scalable vector search engine that leverages Apache Lucene and the JDK's incubator vector API for high-performance vector operations. Designed to efficiently index and search documents using vector-based queries, VecoLuc supports various optimization techniques, including the use of the `jdk.incubator.vector` module to enhance computation speed and accuracy. The application is built using Akka HTTP for handling RESTful APIs, making it easy to integrate with other services. 6 | 7 | ## Features 8 | 9 | - **Vector Search:** Efficient vector-based document indexing and searching. 10 | - **Optimized Vector Operations:** Utilizes the `jdk.incubator.vector` module for performance enhancements. 11 | - **REST API:** Exposes endpoints for document indexing and vector-based search queries. 12 | - **Scalable:** Designed for horizontal scaling with Akka. 13 | 14 | ## Getting Started 15 | 16 | ### Prerequisites 17 | 18 | - **Java 17** or higher (with `jdk.incubator.vector` module support) 19 | - **SBT (Scala Build Tool)** 20 | - **Apache Lucene** (included as a dependency) 21 | 22 | ### Installation 23 | 24 | 1. **Clone the Repository** 25 | ```bash 26 | git clone https://github.com/yourusername/vectoluc.git 27 | cd vectoluc 28 | ``` 29 | 30 | 2. **Build the Project** 31 | Ensure that you have Java 17 or higher installed, and the environment is set up correctly to support the incubator vector module. 32 | ```bash 33 | sbt clean compile 34 | ``` 35 | 36 | ### Building the Project 37 | 38 | To build VecoLuc, execute the following command: 39 | ```bash 40 | sbt compile 41 | ``` 42 | This will compile the project, including any necessary dependencies like Apache Lucene and Akka. 43 | 44 | ### Running the Application 45 | To start the VecoLuc server, run: 46 | 47 | ```bash 48 | sbt run 49 | ``` 50 | The server will start listening for incoming requests on http://0.0.0.0:9000. 51 | 52 | ### Usage 53 | VecoLuc provides a simple RESTful API to index and search documents based on vector embeddings. 54 | 55 | #### Indexing a Document 56 | To index a single document, send a POST request to /index/doc with the document's metadata and vector embeddings. 57 | 58 | #### Request: 59 | 60 | ```bash 61 | curl -X POST http://localhost:9000/index/doc -H "Content-Type: application/json" -d '{ 62 | "metadata": { 63 | "id": "1", 64 | "title": "First Document" 65 | }, 66 | "embeddings": [0.1, 0.2, 0.3, 0.4] 67 | }' 68 | ``` 69 | 70 | #### Indexing Multiple Documents 71 | To index multiple documents at once, send a POST request to /index/docs. 72 | 73 | #### Request: 74 | 75 | ```bash 76 | curl -X POST http://localhost:9000/index/docs -H "Content-Type: application/json" -d '[ 77 | { 78 | "metadata": { 79 | "id": "2", 80 | "title": "Second Document" 81 | }, 82 | "embeddings": [0.2, 0.3, 0.4, 0.5] 83 | }, 84 | { 85 | "metadata": { 86 | "id": "3", 87 | "title": "Third Document" 88 | }, 89 | "embeddings": [0.3, 0.4, 0.5, 0.6] 90 | } 91 | ]' 92 | ``` 93 | 94 | #### Searching for Documents 95 | To search for documents using a vector query, send a POST request to /search with the vector query and the number of top results (topN) in the request body. 96 | 97 | #### Request: 98 | 99 | ```bash 100 | curl -X POST http://localhost:9000/search -H "Content-Type: application/json" -d '{ 101 | "query": [0.1, 0.2, 0.3, 0.4], 102 | "topN": 5 103 | }' 104 | 105 | ``` 106 | #### Response: 107 | 108 | ```json 109 | { 110 | "status": "success", 111 | "latencyMillis": 10, 112 | "data": { 113 | "results": [ 114 | { 115 | "fields": { 116 | "id": "1", 117 | "title": "First Document" 118 | }, 119 | "score": 0.95 120 | } 121 | ], 122 | "count": 1 123 | } 124 | } 125 | ``` 126 | 127 | ### Optimizing Vector Operations 128 | VecoLuc takes advantage of the JDK's jdk.incubator.vector module to optimize vector computations. This module provides high-performance, hardware-accelerated vector operations, making VecoLuc suitable for large-scale vector searches. To ensure the optimizations are enabled, include the following JVM options when running the application: 129 | 130 | ```bash 131 | sbt -J--add-modules=jdk.incubator.vector run 132 | ``` 133 | 134 | This will enable the vector API and ensure that VecoLuc utilizes hardware acceleration where available. 135 | 136 | ### Contributing 137 | 138 | Contributions to VecoLuc are welcome! Please fork the repository and submit a pull request. 139 | 140 | ### License 141 | 142 | VecoLuc is licensed under the Apache License, Version 2.0. See the `LICENSE` file for more details. 143 | 144 | ### Contact 145 | 146 | For questions, issues, or suggestions, feel free to open an issue on GitHub or contact [project maintainer](https://blog.ivan.digital) directly . --------------------------------------------------------------------------------