1use std::collections::{BTreeMap, BTreeSet};
13use std::sync::OnceLock;
14
15use serde::Deserialize;
16use serde_json::{Map, Value};
17
18use crate::SourceAlias;
19
20const FOUNDATION_MODELS_JSON: &str = include_str!("data/foundation-models.json");
21
22pub const CORRESPONDENCE_KINDS: [&str; 4] = ["embedding", "conditional", "encoding", "restatement"];
24
25pub const LOGIC_FAMILIES: [&str; 3] = ["logic-model", "proof-system", "universe-model"];
31
32#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
34#[serde(deny_unknown_fields)]
35pub struct FoundationFamily {
36 pub id: String,
38 pub definition: String,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
44#[serde(rename_all = "camelCase", deny_unknown_fields)]
45pub struct FoundationModel {
46 pub id: String,
48 pub family: String,
50 pub definition: String,
52 pub properties: Map<String, Value>,
54 pub source_aliases: Vec<SourceAlias>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
60#[serde(rename_all = "camelCase", deny_unknown_fields)]
61pub struct FoundationCorrespondence {
62 pub from: String,
64 pub to: String,
66 pub kind: String,
68 pub condition: String,
70 #[serde(default)]
72 pub translation_encodings: Vec<String>,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
77#[serde(rename_all = "camelCase", deny_unknown_fields)]
78pub struct FoundationDistinction {
79 pub models: Vec<String>,
81 pub reason: String,
83 #[serde(default)]
85 pub translation_encodings: Vec<String>,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
90#[serde(deny_unknown_fields)]
91pub struct LanguageFoundation {
92 pub language: String,
94 pub models: Vec<String>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
100#[serde(deny_unknown_fields)]
101pub struct FoundationRegister {
102 pub description: String,
104 pub families: Vec<FoundationFamily>,
106 pub models: Vec<FoundationModel>,
108 pub correspondences: Vec<FoundationCorrespondence>,
110 pub distinctions: Vec<FoundationDistinction>,
112 pub languages: Vec<LanguageFoundation>,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct FoundationProblem {
119 pub kind: &'static str,
121 pub subject: String,
123 pub message: String,
125}
126
127#[must_use]
129pub fn foundation_register() -> &'static FoundationRegister {
130 static REGISTER: OnceLock<FoundationRegister> = OnceLock::new();
131 REGISTER.get_or_init(|| {
132 serde_json::from_str(FOUNDATION_MODELS_JSON)
133 .expect("the embedded foundation models are valid")
134 })
135}
136
137#[must_use]
139pub fn foundation_model(id: &str) -> Option<&'static FoundationModel> {
140 foundation_register()
141 .models
142 .iter()
143 .find(|model| model.id == id)
144}
145
146#[must_use]
148pub fn language_foundation_models(
149 language: &str,
150 family: Option<&str>,
151) -> Vec<&'static FoundationModel> {
152 foundation_register()
153 .languages
154 .iter()
155 .filter(|entry| entry.language == language)
156 .flat_map(|entry| entry.models.iter().filter_map(|id| foundation_model(id)))
157 .filter(|model| family.is_none_or(|family| model.family == family))
158 .collect()
159}
160
161#[must_use]
163pub fn foundation_correspondences(a: &str, b: &str) -> Vec<&'static FoundationCorrespondence> {
164 foundation_register()
165 .correspondences
166 .iter()
167 .filter(|entry| (entry.from == a && entry.to == b) || (entry.from == b && entry.to == a))
168 .collect()
169}
170
171#[must_use]
180pub fn check_foundation_models(register: &FoundationRegister) -> Vec<FoundationProblem> {
181 let mut problems = Vec::new();
182 let mut report = |kind, subject: &str, message: String| {
183 problems.push(FoundationProblem {
184 kind,
185 subject: subject.to_owned(),
186 message,
187 });
188 };
189 let mut families = BTreeSet::new();
190 for family in ®ister.families {
191 if !families.insert(family.id.as_str()) {
192 report(
193 "duplicate",
194 &family.id,
195 format!("family {} is declared twice", family.id),
196 );
197 }
198 }
199 let mut models = BTreeMap::new();
200 for model in ®ister.models {
201 if models.insert(model.id.as_str(), model).is_some() {
202 report(
203 "duplicate",
204 &model.id,
205 format!("model {} is declared twice", model.id),
206 );
207 }
208 if !families.contains(model.family.as_str()) {
209 report(
210 "unknown-family",
211 &model.id,
212 format!("{} belongs to undeclared family {}", model.id, model.family),
213 );
214 } else if !model.id.starts_with(&format!("{}.", model.family)) {
215 report(
216 "identity",
217 &model.id,
218 format!(
219 "{} is not named under its family {}",
220 model.id, model.family
221 ),
222 );
223 }
224 if model.definition.is_empty() {
225 report(
226 "definition",
227 &model.id,
228 format!("{} has no definition", model.id),
229 );
230 }
231 if model.properties.is_empty() {
232 report(
233 "properties",
234 &model.id,
235 format!(
236 "{} records no properties, so its distinctness cannot be checked",
237 model.id
238 ),
239 );
240 }
241 }
242 for (index, model) in register.models.iter().enumerate() {
243 for other in ®ister.models[index + 1..] {
244 if model.family == other.family && model.properties == other.properties {
246 report(
247 "indistinct-models",
248 &model.id,
249 format!(
250 "{} and {} record the same properties: one meaning must be one model with source aliases",
251 model.id, other.id
252 ),
253 );
254 }
255 }
256 }
257 let mut pairs = BTreeSet::new();
258 for entry in ®ister.correspondences {
259 let subject = format!("{} -> {}", entry.from, entry.to);
260 for end in [&entry.from, &entry.to] {
261 if !models.contains_key(end.as_str()) {
262 report(
263 "unknown-model",
264 &subject,
265 format!("{subject} names undeclared model {end}"),
266 );
267 }
268 }
269 if entry.from == entry.to {
270 report(
271 "self-correspondence",
272 &subject,
273 format!("{subject} relates a model to itself"),
274 );
275 }
276 if !CORRESPONDENCE_KINDS.contains(&entry.kind.as_str()) {
277 report(
278 "correspondence-kind",
279 &subject,
280 format!(
281 "{subject} has kind {}; two distinct models never correspond exactly, and the same meaning must be one model",
282 entry.kind
283 ),
284 );
285 }
286 if entry.condition.trim().chars().count() < 20 {
287 report(
288 "unconditioned-correspondence",
289 &subject,
290 format!("{subject} does not state the condition under which the models agree"),
291 );
292 }
293 let key = if entry.from <= entry.to {
294 (&entry.from, &entry.to)
295 } else {
296 (&entry.to, &entry.from)
297 };
298 if !pairs.insert(key) {
299 report(
300 "duplicate",
301 &subject,
302 format!("{subject} is recorded twice"),
303 );
304 }
305 }
306 for entry in ®ister.distinctions {
307 let subject = entry.models.join(" / ");
308 if entry.models.len() != 2 {
309 report(
310 "distinction",
311 &subject,
312 "a distinction names exactly two models".to_owned(),
313 );
314 }
315 for id in &entry.models {
316 if !models.contains_key(id.as_str()) {
317 report(
318 "unknown-model",
319 &subject,
320 format!("distinction {subject} names undeclared model {id}"),
321 );
322 }
323 }
324 if entry.reason.trim().chars().count() < 20 {
325 report(
326 "distinction",
327 &subject,
328 format!("distinction {subject} gives no reason"),
329 );
330 }
331 }
332 for entry in ®ister.languages {
333 let mut used = BTreeSet::new();
334 for id in &entry.models {
335 match models.get(id.as_str()) {
336 Some(model) => {
337 used.insert(model.family.as_str());
338 }
339 None => report(
340 "unknown-model",
341 &entry.language,
342 format!("{} uses undeclared model {id}", entry.language),
343 ),
344 }
345 }
346 for family in &families {
347 if !used.contains(family) {
348 report(
349 "language-family",
350 &entry.language,
351 format!("{} does not record its {family}", entry.language),
352 );
353 }
354 }
355 }
356 for family in LOGIC_FAMILIES
357 .into_iter()
358 .filter(|family| families.contains(family))
359 {
360 let members: Vec<_> = register
361 .models
362 .iter()
363 .filter(|model| model.family == family)
364 .collect();
365 if members.len() < 2 {
366 continue;
367 }
368 for model in &members {
369 let embedded = members
370 .iter()
371 .filter(|other| other.id != model.id)
372 .all(|other| {
373 register.correspondences.iter().any(|entry| {
374 entry.kind == "embedding" && entry.from == other.id && entry.to == model.id
375 })
376 });
377 if embedded {
378 report(
379 "universal-model",
380 &model.id,
381 format!(
382 "every other {family} embeds into {}, which makes it a hard-coded universal logic",
383 model.id
384 ),
385 );
386 }
387 if family != "logic-model"
388 && register.languages.len() > 1
389 && register
390 .languages
391 .iter()
392 .all(|entry| entry.models.contains(&model.id))
393 {
394 report(
395 "universal-model",
396 &model.id,
397 format!(
398 "every language is made to use {}, which makes it a hard-coded universal foundation",
399 model.id
400 ),
401 );
402 }
403 }
404 }
405 problems
406}