# `relate`: find relationships among named entities.

*Paul McCartney sings Yesterday, and Ringo Starr sings Octopus's Garden. The two wrong pairs do not reach the default threshold.*

```
sings="sings=singer:song"

cat <<'EOF' |
[
  {"name": "Paul McCartney", "kind": "singer"},
  {"name": "Ringo Starr", "kind": "singer"},
  {"name": "Yesterday", "kind": "song"},
  {"name": "Octopus's Garden", "kind": "song"}
]
EOF
thinkthen relate "$sings" |
jq -c '[.source.name, .target.name, .probability]'
```

*Output*

```
["Paul McCartney","Yesterday",0.81]
["Ringo Starr","Octopus's Garden",0.88]
```

*exit 0*

You give it a set of names, the kind of each name, and the relations you care about. `relate` reads no other text. Jev answers from what it knows about the names. You get back one edge for each related pair, with its probability. A rule asks one yes or no question for each pair. In a rules file, a rule marked `"single": true` asks one choice for each source instead, and gives that source at most one edge. For the links a text states, use `recognize --relation`.

## Read the answer

A relation has a direction, or it reads the same both ways. The number on an edge is a probability. Your threshold decides which edges you keep.

| Exit code | What it means |
| --- | --- |
| 0 | the run finished |
| 6 | the run finished with failed questions |
| 2 | usage or input error |
| 4 | the backend failed or refused, as it does for evidence over the size limit |
| 5 | a local failure |
| 70 | a defect in the tool |

## Call it from your language

The Bash example opens this page.

```
import thinkthen as tt

names = [
    ("Paul McCartney", "singer"),
    ("Ringo Starr", "singer"),
    ("Yesterday", "song"),
    ("Octopus's Garden", "song"),
]
who_sings = tt.relate(
    names,
    relations={"sings": ("singer", "song")},
).value
sings = [
    (edge.source.name, edge.target.name)
    for edge in who_sings
]
assert sings == [
    ("Paul McCartney", "Yesterday"),
    ("Ringo Starr", "Octopus's Garden"),
]
```

No Polars sample for `relate`.

No pandas sample for `relate`.

```
import assert from "node:assert/strict";
import * as tt from "thinkthen";

const rules = [
  "Book economy class for every flight under six hours.",
  "Submit receipts within 30 days of the trip.",
  "Hotel stays are capped at 200 dollars a night.",
  "Employees may book business class on any flight.",
  "Rental cars need a manager's approval.",
  "Receipts may be submitted at any time, " +
    "with no deadline.",
  "Meals are reimbursed up to 60 dollars a day.",
  "Use the company travel portal for all bookings.",
];
const entities = rules.map(
  (rule): readonly [string, string] => [rule, "rule"],
);
const contradictions = (await tt.relate(entities, {
  relations: ["contradicts"],
  either: ["contradicts"],
  threshold: 0.5,
})).value;
const pairs = contradictions.map((edge) => [
  edge.source.name,
  edge.target.name,
  edge.probability,
]);
assert.deepEqual(pairs, [
  [rules[0], rules[3], 0.83],
  [rules[1], rules[5], 0.97],
]);
```

```
require "thinkthen"

rules = [
  "Book economy class for every flight under six hours.",
  "Submit receipts within 30 days of the trip.",
  "Hotel stays are capped at 200 dollars a night.",
  "Employees may book business class on any flight.",
  "Rental cars need a manager's approval.",
  "Receipts may be submitted at any time, " \
    "with no deadline.",
  "Meals are reimbursed up to 60 dollars a day.",
  "Use the company travel portal for all bookings."
]
entities = rules.map { |rule| [rule, "rule"] }
contradictions = ThinkThen.relate(
  entities,
  relations: ["contradicts"],
  either: ["contradicts"],
  threshold: 0.5
).value
pairs = contradictions.map do |edge|
  [edge.source.name, edge.target.name, edge.probability]
end
raise unless pairs == [
  [rules[0], rules[3], 0.83],
  [rules[1], rules[5], 0.97]
]
```

```
library(thinkthen)

rules <- c(
  "Book economy class for every flight under six hours.",
  "Submit receipts within 30 days of the trip.",
  "Hotel stays are capped at 200 dollars a night.",
  "Employees may book business class on any flight.",
  "Rental cars need a manager's approval.",
  paste0(
    "Receipts may be submitted at any time, ",
    "with no deadline."
  ),
  "Meals are reimbursed up to 60 dollars a day.",
  "Use the company travel portal for all bookings."
)
entities <- data.frame(name = rules, kind = "rule")
contradictions <- tt_relate(
  entities,
  either = "contradicts",
  threshold = 0.5
)$value
stopifnot(identical(contradictions$source, rules[c(1, 2)]))
stopifnot(identical(contradictions$target, rules[c(4, 6)]))
stopifnot(identical(
  contradictions$probability,
  c(0.83, 0.97)
))
```

```
use thinkthen::{Engine, Entity, Relate, RelationRule};

let tt = Engine::from_env()?;
let rules = [
    "Book economy class for every flight under six hours.",
    "Submit receipts within 30 days of the trip.",
    "Hotel stays are capped at 200 dollars a night.",
    "Employees may book business class on any flight.",
    "Rental cars need a manager's approval.",
    concat!(
        "Receipts may be submitted at any time, ",
        "with no deadline.",
    ),
    "Meals are reimbursed up to 60 dollars a day.",
    "Use the company travel portal for all bookings.",
];
let contradicts =
    RelationRule::both_ways("contradicts", "*", "*")?;
let ask = Relate::builder()
    .relation(contradicts)?
    .threshold(0.5)?
    .build()?;
let entities = rules
    .iter()
    .map(|rule| Entity::new(rule, "rule"))
    .collect::<Result<Vec<_>, _>>()?;
let contradictions = tt.relate(&ask, entities)?
    .into_value();

let pairs: Vec<_> = contradictions
    .iter()
    .map(|edge| {
        let source = edge.source().name();
        let target = edge.target().name();
        (source, target, edge.probability())
    })
    .collect();
let expected = [
    (rules[0], rules[3], 0.83),
    (rules[1], rules[5], 0.97),
];
assert_eq!(pairs, expected);
```

Put this code inside `fn main() -> Result<(), Box<dyn std::error::Error>>` and end it with `Ok(())`. `main` returns a `Result`, so `?` compiles.

```
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <thinkthen.h>

thinkthen_engine *tt = thinkthen_engine_new();
assert(tt);

const char *rules[] = {
    "Book economy class for every flight under six hours.",
    "Submit receipts within 30 days of the trip.",
    "Hotel stays are capped at 200 dollars a night.",
    "Employees may book business class on any flight.",
    "Rental cars need a manager's approval.",
    "Receipts may be submitted at any time, "
        "with no deadline.",
    "Meals are reimbursed up to 60 dollars a day.",
    "Use the company travel portal for all bookings.",
};
char records[8][96];
const char *texts[8];
size_t lengths[8];
for (int i = 0; i < 8; i++) {
    snprintf(
        records[i],
        sizeof records[i],
        "{\"name\": \"%s\", \"kind\": \"rule\"}",
        rules[i]
    );
    texts[i] = records[i];
    lengths[i] = strlen(records[i]);
}
const char *spec =
    "{\"version\": 1, \"relate\": {\"relations\": [{"
    "\"name\": \"contradicts\", \"source\": \"*\", "
    "\"target\": \"*\", \"either\": true}]}, "
    "\"threshold\": 0.5}";
char *contradictions;
size_t contradictions_len;
int rc = thinkthen_relate(
    tt,
    spec,
    texts,
    lengths,
    8,
    &contradictions,
    &contradictions_len
);
assert(rc == THINKTHEN_OK);
assert(strstr(contradictions, "\"probability\":0.83"));
assert(strstr(contradictions, "\"probability\":0.97"));
thinkthen_free_string(contradictions);
thinkthen_engine_free(tt);
```

Keep the `#include` lines on top. Put the rest inside `int main(void)` and end it with `return 0;`.

No C++ sample for `relate`.

No Objective-C sample for `relate`.

No COBOL sample for `relate`.

No Ada sample for `relate`.

No Java sample for `relate`.

No Kotlin sample for `relate`.

No Scala sample for `relate`.

No C# sample for `relate`.

No Go sample for `relate`.

No Swift sample for `relate`.

No Zig sample for `relate`.

No PHP sample for `relate`.

No Dart sample for `relate`.

```
LOAD './thinkthen.duckdb_extension';

CREATE TABLE rules AS FROM (VALUES
    (1, 'Book economy class for every flight ' ||
        'under six hours.'),
    (2, 'Submit receipts within 30 days of the trip.'),
    (3, 'Hotel stays are capped at 200 dollars a night.'),
    (4, 'Employees may book business class on any flight.'),
    (5, 'Rental cars need a manager''s approval.'),
    (6, 'Receipts may be submitted at any time, ' ||
        'with no deadline.'),
    (7, 'Meals are reimbursed up to 60 dollars a day.'),
    (8, 'Use the company travel portal for all bookings.')
) r(id, body);

SELECT * FROM thinkthen_relate(
    'SELECT id, body AS name, ''rule'' AS kind FROM rules',
    '{"version": 1, "relate": {"relations": [{' ||
    '"name": "contradicts", "source": "*", ' ||
    '"target": "*", "either": true}]}}'
) AS contradiction;
```

*What DuckDB printed*

```
relation|source|target|probability|either
contradicts|1|4|0.83|true
contradicts|2|6|0.97|true
```

```
.load ./thinkthen

CREATE TABLE rules(id INTEGER, body TEXT, kind TEXT);
INSERT INTO rules VALUES
    (1, 'Book economy class for every flight ' ||
        'under six hours.', 'rule'),
    (2, 'Submit receipts within 30 days of the trip.',
        'rule'),
    (3, 'Hotel stays are capped at 200 dollars a night.',
        'rule'),
    (4, 'Employees may book business class on any flight.',
        'rule'),
    (5, 'Rental cars need a manager''s approval.', 'rule'),
    (6, 'Receipts may be submitted at any time, ' ||
        'with no deadline.', 'rule'),
    (7, 'Meals are reimbursed up to 60 dollars a day.',
        'rule'),
    (8, 'Use the company travel portal for all bookings.',
        'rule');

SELECT * FROM thinkthen_relate(
    'SELECT id, body AS name, kind FROM rules',
    'either:contradicts'
) AS contradiction;
```

*What SQLite printed*

```
contradicts|1|4|0.83|1
contradicts|2|6|0.97|1
```

```
CREATE TABLE rules (
    id int, body text, kind text DEFAULT 'rule'
);
INSERT INTO rules (id, body) VALUES
    (1, 'Book economy class for every flight ' ||
        'under six hours.'),
    (2, 'Submit receipts within 30 days of the trip.'),
    (3, 'Hotel stays are capped at 200 dollars a night.'),
    (4, 'Employees may book business class on any flight.'),
    (5, 'Rental cars need a manager''s approval.'),
    (6, 'Receipts may be submitted at any time, ' ||
        'with no deadline.'),
    (7, 'Meals are reimbursed up to 60 dollars a day.'),
    (8, 'Use the company travel portal for all bookings.');

SELECT * FROM thinkthen_relate(
    'SELECT id, body AS name, kind FROM rules',
    '{"version": 1, "relate": {"relations": [{' ||
    '"name": "contradicts", "source": "*", ' ||
    '"target": "*", "either": true}]}}'
) AS contradiction;
```

*What PostgreSQL printed*

```
  relation   | source | target | probability | either 
-------------+--------+--------+-------------+--------
 contradicts |      1 |      4 |        0.83 | t
 contradicts |      2 |      6 |        0.97 | t
(2 rows)
```

[Arguments, options, and more examples](/reference/functions/relate/)

Watch `relate` answer questions about Beatles songs: [relate asks what Jev knows about the names.](/learn/beatles-bench/relate/)

On GitHub: [github.com/botassembly/thinkthen](https://github.com/botassembly/thinkthen)
