Search code examples
algorithmneo4jneo4jclient

Calling algo.similarity.jaccard algorithm from neo4j C# client doesn't work


I have the following query and it's working perfectly when I execute it on Neoj4 Browser:

MATCH (p1:Book{friendlyUrl:"a-modern-witch"})-[r:BOOK_ADDED_TO_CATALOG]->(catalog1:Catalog)
WITH p1, collect(id(catalog1)) AS p1Catalog
MATCH (p2:Book)-[:BOOK_ADDED_TO_CATALOG]->(catalog2:Catalog)
WHERE p1 <> p2
WITH p1, p1Catalog, p2, collect(id(catalog2)) AS p2Catalog
RETURN p1.title AS from,
p2.title AS to,
algo.similarity.jaccard(p1Catalog, p2Catalog) AS similarity

ORDER BY similarity DESC

I want to transform it to c# code and I am doing it like this:

var result = _graphClient.Cypher
        .Match ("(p1:Book{friendlyUrl:'a-modern-witch'})-[r:BOOK_ADDED_TO_CATALOG]->(catalog1:Catalog)")
        .With ("p1, collect(id(catalog1)) AS p1Catalog")
        .Match ("(p2:Book)-[:BOOK_ADDED_TO_CATALOG]->(catalog2:Catalog)")
        .Where ("p1 <> p2")
        .With ("p1, p1Catalog, p2, collect(id(catalog2)) AS p2Catalog")
        .Call ("algo.similarity.jaccard(p1Catalog, p2Catalog)")
        .Yield ("value AS similiarity")
        .Return ((p1, p2, similiarity) => new {
          bk1 = p1.As<BookDetailsDto> (),
          bk2 = p2.As<BookDetailsDto> (),
        })
        .Limit (10);

Unfortunately when I call it through the API I am getting the following error:

An unhandled exception occurred while processing the request. NeoException: SyntaxException: Type mismatch: expected List, List or List but was List (line 6, column 30 (offset: 327)) CALL algo.similarity.jaccard(p1Catalog, p2Catalog)

The cypher query that is build by neo4jClient is quite different than the original one on the top:

MATCH (p1:Book{friendlyUrl:'harry-potter-and-the-deathly-hallows'})-[r:BOOK_ADDED_TO_CATALOG]->(catalog1:Catalog)
WITH p1, collect(id(catalog1)) AS p1Catalog
MATCH (p2:Book)-[:BOOK_ADDED_TO_CATALOG]->(catalog2:Catalog)
WHERE p1 <> p2
WITH p1, p1Catalog, p2, collect(id(catalog2)) AS p2Catalog
CALL algo.similarity.jaccard(p1Catalog, p2Catalog)
YIELD value AS similiarity
RETURN p1 AS bk, p2 AS bk2
LIMIT 10

Solution

  • OK, so there are two parts to this - why the Cypher doesn't match, and what the error means - they're both related, but we'll tackle the Cypher difference first - as that'll get your results.

    In your Cypher you don't do a CALL - you simply RETURN the Jaccard, so to get a true reflection of your Cypher, you'd want:

    var result = _graphClient.Cypher
        .Match ("(p1:Book{friendlyUrl:'a-modern-witch'})-[r:BOOK_ADDED_TO_CATALOG]->(catalog1:Catalog)")
        .With ("p1, collect(id(catalog1)) AS p1Catalog")
        .Match ("(p2:Book)-[:BOOK_ADDED_TO_CATALOG]->(catalog2:Catalog)")
        .Where ("p1 <> p2")
        .With ("p1, p1Catalog, p2, collect(id(catalog2)) AS p2Catalog")
        .Return ((p1, p2, similiarity) => new {
          bk1 = p1.As<BookDetailsDto> (),
          bk2 = p2.As<BookDetailsDto> (),
          similarity = Return.As<double>("algo.similarity.jaccard(p1Catalog, p2Catalog)")
        })
        .OrderByDescending("similarity")
        .Limit (10);
    

    The key bit in there is the removal of the .Call err, call, and the use of Return.As<> for your algorithm call.

    The second part - (the error message you get) is directly related to this subtle change, by using CALL you switch from using the 'Function' version of Jaccard to the 'Prodedure' version - and they have different signatures, the Function version looks like this:

    "algo.similarity.jaccard(vector1 :: LIST? OF NUMBER?, vector2 :: LIST? OF NUMBER?) :: (FLOAT?)"
    

    So it expects a LIST? OF NUMBER? what your COLLECT(id(catalog)) outputs. You can get this information by running the following Cypher in your browser:

    CALL dbms.functions() YIELD name, signature, description
    WHERE name CONTAINS 'jaccard'
    RETURN *
    

    Now, the Prodedure version looks like this:

    "algo.similarity.jaccard(data = null :: LIST? OF MAP?, config = {} :: MAP?) :: (nodes :: INTEGER?, sourceNodes :: INTEGER?, targetNodes :: INTEGER?, similarityPairs :: INTEGER?, computations :: INTEGER?, write :: BOOLEAN?, writeRelationshipType :: STRING?, writeProperty :: STRING?, min :: FLOAT?, max :: FLOAT?, mean :: FLOAT?, stdDev :: FLOAT?, p25 :: FLOAT?, p50 :: FLOAT?, p75 :: FLOAT?, p90 :: FLOAT?, p95 :: FLOAT?, p99 :: FLOAT?, p999 :: FLOAT?, p100 :: FLOAT?)"
    

    Which is looking for a LIST? OF MAP? - which is not what you're getting - hence the error message.

    Cypher for this is:

    CALL dbms.procedures() YIELD name, signature, description
    WHERE name CONTAINS 'jaccard'
    RETURN *
    

    Anyhews - the first C# code should get you sorted.