Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion metatomic-core/src/c_api/model.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::ffi::{c_void, c_char};
use metatensor::c_api::{mts_labels_t, mts_tensormap_t};

use crate::c_api::catch_unwind;
use crate::Error;

use super::{mta_status_t, mta_string_t, mta_system_t};

/// A model that computes physical properties of atomistic systems.
Expand Down Expand Up @@ -209,5 +212,16 @@ pub unsafe extern "C" fn mta_format_metadata(
metadata: *const c_char,
printed: *mut mta_string_t,
) -> mta_status_t {
todo!()
catch_unwind(|| {
check_pointers_non_null!(metadata, printed);
let metadata_cstr = std::ffi::CStr::from_ptr(metadata);
let metadata_str = metadata_cstr.to_str().map_err(|_| {
Error::InvalidParameter("metadata is not valid UTF-8".into())
})?;
let metadata_json = json::parse(metadata_str).map_err(|e| {
Error::Serialization(format!("invalid JSON for ModelMetadata: {e}"))
})?;
*printed = mta_string_t::new(crate::ModelMetadata::try_from(&metadata_json)?.print());
Ok(())
})
}
192 changes: 189 additions & 3 deletions metatomic-core/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,63 @@ impl<'a> TryFrom<&'a JsonValue> for References {
}


fn normalize_whitespace(data: &str) -> String {
let mut normalized_string = String::new();
for c in data.chars() {
if c == '\n' || c == '\r' || c == '\t' {
normalized_string.push(' ');
} else {
normalized_string.push(c);
}
}
normalized_string
}


fn wrap_80_chars(output: &mut String, data: &str, indent: &str) -> Result<(), Error> {
let string = normalize_whitespace(data);
let mut chars: Vec<char> = string.chars().collect();
let line_length = 80 - indent.len();
assert!(line_length > 50);
let mut first_line = true;
loop {
if chars.len() <= line_length {
// last line
if !first_line {
output.push_str(indent);
}
output.extend(chars);
break;
} else {
// backtrack to find the end of a word
let mut word_found = false;
for i in (0..line_length - 1).rev() {
if chars[i] == ' ' {
word_found = true;
// print the current line
if !first_line {
output.push_str(indent);
}
output.extend(chars.drain(0..i));
output.push('\n');
// remove the space
chars.remove(0);
first_line = false;
break;
}
}

if !word_found {
// this is only hit if a single word takes a full line.
return Err(Error::InvalidParameter("some words are too long to be wrapped, make them shorter".into()));
}
}
}

Ok(())
}


/// Metadata about a model
#[derive(Debug, Clone)]
pub struct ModelMetadata {
Expand Down Expand Up @@ -270,13 +327,108 @@ impl<'a> TryFrom<&'a JsonValue> for ModelMetadata {
extra.insert(key.to_string(), value.to_string());
}

Ok(ModelMetadata {
let metadata = ModelMetadata {
name: name.to_string(),
authors: authors,
description: description,
references: references,
extra: extra,
})
};
metadata.validate()?;
Ok(metadata)
}
}

impl ModelMetadata{
fn validate(&self) -> Result<(), Error> {
for author in &self.authors {
if author.is_empty() {
return Err(Error::InvalidParameter("author can not be empty string in ModelMetadata".into()));
}
}

let References {
model,
architecture,
implementation,
} = &self.references;
for m in model.iter() {
if m.is_empty() {
return Err(Error::InvalidParameter("reference can not be empty string (in 'model' section)".into()));
}
}
for a in architecture.iter() {
if a.is_empty() {
return Err(Error::InvalidParameter("reference can not be empty string (in 'architecture' section)".into()));
}
}
for i in implementation.iter() {
if i.is_empty() {
return Err(Error::InvalidParameter("reference can not be empty string (in 'implementation' section)".into()));
}
}

Ok(())
}
pub fn print(&self) -> String {
let mut output = String::new();
if self.name.is_empty() {
output.push_str("This is an unnamed model\n");
output.push_str("========================\n");
} else {
output.push_str(&format!("This is the {} model\n", &self.name));
output.push_str(&format!("============{}======\n", "=".repeat(self.name.len())));
}
if !self.description.is_empty() {
output.push_str("\n");
let _ = wrap_80_chars(&mut output, &(self.description), "");
output.push_str("\n");
}

if !self.authors.is_empty() {
output.push_str("\nModel authors\n-------------\n\n");
for author in self.authors.iter() {
output.push_str("- ");
let _ = wrap_80_chars(&mut output, &author, " ");
output.push_str("\n");
}
}

let mut references_output = String::new();
if !self.references.model.is_empty() {
references_output.push_str("- about this specific model:\n");
for reference in self.references.model.iter() {
references_output.push_str(" * ");
let _ = wrap_80_chars(&mut references_output, &reference, " ");
references_output.push_str("\n");
}
}

if !self.references.architecture.is_empty() {
references_output.push_str("- about the architecture of this model:\n");
for reference in self.references.architecture.iter() {
references_output.push_str(" * ");
let _ = wrap_80_chars(&mut references_output, reference, " ");
references_output.push_str("\n");
}
}

if !self.references.implementation.is_empty() {
references_output.push_str("- about the implementation of this model:\n");
for reference in self.references.implementation.iter() {
references_output.push_str(" * ");
let _ = wrap_80_chars(&mut references_output, reference, " ");
references_output.push_str("\n");
}
}

if !references_output.is_empty() {
output.push_str("\nModel references\n----------------\n\n");
output.push_str("Please cite the following references when using this model:\n");
output.push_str(&references_output);
}

output
}
}

Expand Down Expand Up @@ -486,6 +638,7 @@ impl<'a> TryFrom<&'a JsonValue> for ModelCapabilities {
}
}


#[cfg(test)]
mod tests {
mod pair_list_options {
Expand Down Expand Up @@ -602,7 +755,8 @@ mod tests {
}

mod model_metadata {
use super::super::*;

use super::super::*;

fn example() -> ModelMetadata {
ModelMetadata {
Expand Down Expand Up @@ -704,6 +858,38 @@ mod tests {
assert_eq!(error.to_string(), expected);
}
}

#[test]
fn printing() {
let metadata = example();
let output = metadata.print();
let expected = String::from(
"This is the test-model model
============================

A test model

Model authors
-------------

- Alice
- Bob <bob@test.com>

Model references
----------------

Please cite the following references when using this model:
- about this specific model:
* doi:10.1234/test
- about the architecture of this model:
* doi:10.1234/arch
- about the implementation of this model:
* https://github.com/test
"
);

assert_eq!(output, expected);
}
}

mod model_capabilities {
Expand Down
46 changes: 46 additions & 0 deletions metatomic-core/tests/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,49 @@ TEST_CASE("mta_unit_conversion_factor") {
"invalid parameter: dimension mismatch in unit conversion: "
"'m' has dimension [L] but 'kg' has dimension [M]");
}

TEST_CASE("mta_format_metadata") {
std::string json =R"({
"type": "metatomic_model_metadata",
"name": "name",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.",
"authors": ["Short author", "Some extremely long author that will take more than one line in the printed output"],
"references": {
"architecture": ["ref-2", "ref-3"],
"model": ["a very long reference that will take more than one line in the printed output"],
"implementation": []
},
"extra": {}
})";
auto* mta_string = mta_string_create("");
REQUIRE(mta_string != nullptr);
auto status = mta_format_metadata(json.c_str(), &mta_string);
REQUIRE(status == MTA_SUCCESS);
const auto expected = R"(This is the name model
======================

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation.

Model authors
-------------

- Short author
- Some extremely long author that will take more than one line in the printed
output

Model references
----------------

Please cite the following references when using this model:
- about this specific model:
* a very long reference that will take more than one line in the printed
output
- about the architecture of this model:
* ref-2
* ref-3
)";
CHECK(std::string(mta_string_view(mta_string)) == expected);
mta_string_free(mta_string);
}
Loading