A configuration schema for preCICE adapters and tools

DOI

Summary: We have been developing a JSON schema for preCICE adapters and tools improving interoperability and enabling tooling support. Find out more about the schema and what you can do with it. The schema is not finalized yet, we need your feedback now.


As part of the ecosystem standardization (preECO project), we have been developing a JSON schema: the preCICE adapter configuration schema. To get a first impression, here is a visualization of the main part of the schema:

The schema defines the semantics of configuration files for adapters and tools. Let’s look at an example configuration file:

{
  "participant_name": "Fluid",
  "precice_config_file_name": "../precice-config.xml",
  "interfaces": [
    {
      "mesh_name": "Fluid-Mesh",
      "patches": [
        "interface"
      ],
      "location": "faceCenters",
      "write_data": [
        {
          "name": "Temperature"
        }
      ],
      "read_data": [
        {
          "name": "Heat-Flux"
        }
      ]
    }
  ]
}

Semantics means, for example, that it has to be precice_config_file_name and not precice_config_name. Or that write_data is a list and not a single object. It is not so much about languages. The example happens to be in JSON, but the schema also covers YAML:

participant_name: Fluid
precice_config_file_name: ../precice-config.xml
interfaces:
  - mesh_name: Fluid-Mesh
    patches:
      - interface
    location: faceCenters
    write_data:
      - name: Temperature
    read_data:
      - name: Heat-Flux

Even more languages can be covered through conversions.

Why? What can I do with a schema?

Many things. An incomplete list:

  1. A schema defines how a configuration file should look like. It, thus, replaces a lot of explicit documentation: Instead of writing documentation on your adapter configuration, you simply say that it adheres to the preCICE adapter configuration schema. The schema itself contains documentation, for instance:
precice_config_file_name:
  type: string
  description: "Path to the preCICE configuration file relative to the current working directory of the coupled solver."
  default: "../precice-config.xml"
  1. If several adapters and tools do this, then configurations become interoperable, reducing mental load. You can copy the adapter config from a FEniCS tutorial case and directly use for your inhouse solid solver.
  2. You can exploit the power of Large language models (LLMs) as soon as a schema defines a pattern for your data. For example, you can automatically convert between different configuration languages. Converting a preciceDict used by the OpenFOAM adapter into a JSON configuration file that the SU2 adapter understands will be possible as soon as both adapter configurations adhere to the schema.
  3. You can use the schema to validate configuration files and, thus, replace explicit configuration checks in your adapter code. For instance, by using the Python library jsonschema:
import json
from jsonschema import validate
from jsonschema.exceptions import ValidationError, SchemaError

try:
    with open("precice_adapter_configuration_schema.json", "r") as schema_f:
        schema = json.load(schema_f)
    
    with open("precice-adapter-config.json", "r") as config_f:
        config = json.load(config_f)
    
    validate(instance=config, schema=schema)
    print("Validation successful: The configuration file is valid.")

except ValidationError as ve:
    print(f"Validation error: {ve.message}")
except SchemaError as se:
    print(f"Schema error: {se.message}")
except json.JSONDecodeError as je:
    print(f"Invalid JSON format: {je.msg}")
  1. A schema enables functionalities in tons of tools (e.g. IDEs or GUIs). As an example, take the MetaConfigurator. Load it with the schema, you get a nice configuration GUI. This is also the best way to learn about all details of the schema.

Porting existing adapters and tools

None of the existing adapters and tools in the preCICE ecosystem completely follow the schema yet. For most projects, we will need new major releases as we will need to break backwards compatibility. Let’s look at three examples to get an idea.

FEniCS adapter

Here, we are pretty close already:

Old:

{
  "participant_name": "Solid",
  "config_file_name": "../precice-config.xml",
  "interface": {
      "coupling_mesh_name": "Solid-Mesh",
      "read_data_name": "Temperature",
      "write_data_name": "Heat-Flux"
  }
}

New:

{
  "participant_name": "Solid",
  "precice_config_file_name": "../precice-config.xml",
  "interfaces": [
    {
      "mesh_name": "Solid-Mesh",
      "read_data": [
        {
          "name": "Temperature"
        }
      ],
      "write_data": [
        {
          "name": "Heat-Flux"
        }
      ]
    }
  ]
}

You might complain:

But the FEniCS adapter does not yet support multiple interfaces.

Correct, but this is actually a feature, not a bug. We now could throw an explicit “not yet supported” error in the adapter. Users do not need to wonder and ask for help, but they can simply try it. And if we add such a standard feature in the future, the configuration is already there.

Nutils solvers

Currently, all Nutils solvers use preCICE in a hard-coded sense. Configuration data is, thus, hard-coded as well. There is no dedicated Nutils adapter. If ever necessary, any solver could change to the JSON schema, for example, as follows:

import json
from dataclasses import dataclass
from typing import List, Optional

@dataclass
class PreciceData:
    name: str
    solver_name: Optional[str] = None
    operation: Optional[str] = None

@dataclass
class InterfaceConfig:
    mesh_name: str
    patches: List[str]
    location: Optional[str] = None
    write_data: List[PreciceData]
    read_data: List[PreciceData]
    is_received: Optional[bool] = None

@dataclass
class Config:
    participant_name: str
    precice_config_file_name: str
    interfaces: List[InterfaceConfig]

def read_config(file_path: str) -> Config:
    with open(file_path, 'r') as file:
        data = json.load(file)

    interfaces = [
        InterfaceConfig(
            mesh_name=interface["mesh_name"],
            patches=interface.get("patches", []),
            location=interface.get("location"),
            write_data=[PreciceData(**wd) for wd in interface.get("write_data", [])],
            read_data=[PreciceData(**rd) for rd in interface.get("read_data", [])],
            is_received=interface.get("is_received")
        )
        for interface in data["interfaces"]
    ]

    return Config(
        participant_name=data["participant_name"],
        precice_config_file_name=data["precice_config_file_name"],
        interfaces=interfaces
    )

config = read_config("precice-adapter-config.json")

Such code generation will likely become part of MetaConfigurator. Also LLMs can typically generate these when you give the schema. In fact, the code above was generated by ChatGPT. One more reason for a schema :flexed_biceps:. We are also thinking about providing such an adapter configuration reader as a small helper tool.

OpenFOAM adapter

The OpenFOAM adapter uses an OpenFOAM dictionary for configuration as OpenFOAM users know very well how to work with these files. We want to follow the schema, but do not want to change the file format. Here, LLMs come in handy:

  • Given the schema, a LLM (we tested with ChatGPT) can convert from OpenFOAM dictionary to JSON. Potential pitfall might be that we cannot use this conversion for validation as the LLM might “fix” simple issues.
  • Given the schema and an OpenFOAM dictionary example, an LLM can convert from JSON to OpenFOAM dictionary.

Both conversions are directly integrated in MetaConfigurator.

There are a few more trivial changes we need to do, such as DisplacementDelta → Displacement-Delta or preciceConfig → preciceConfigFileName.

Last, we need to get rid of modules (CHT) etc., but this is anyway already work in progress.

So, what does this mean for me?

We hope that, at this point, you are convinced that this adapter configuration schema is something useful. Have you already developed an adapter? Please let us know how easy or difficult it is to port your configuration. Is there anything you are missing from the current schema? If you are just starting to develop a new adapter, try to follow the schema directly from the beginning.

Stay up to date: the schema is still evolving, see the current issues. Please don’t hesitate to join discussions or open new issues. As part of the adapter guidelines, we plan to release v1.0 just before the workshop 2025 and from there on, we hope to guarantee backwards compatibility :crossed_fingers:. This means, we need to learn and define as much as possible beforehand. Please join us in this endeavor. :hugs:

3 Likes

Hi @uekerman
If we have developed a adapter for our own code, what should we do to develop the schema that the metaConfigurator can read?

Hi @Gong_CHEN,

The MetaConfigurator is a tool that works independently of preCICE. You can use it to develop any schema or to use any schema.

We are currently developing a schema for preCICE adapters. In the configuration of your adapter, you could use meaning adhere to this schema. This probably means that you have to change a few things. Then, you get all the nice interoperability benefits explained above. If you want, please post an example configuration of your adapter and I could help you identify what needs to be changed.

The MetaConfigurator with the schema already preloaded: MetaConfigurator

The schema itself: preeco-orga/adapter-config-schema/precice_adapter_config_schema.json at main · precice/preeco-orga · GitHub

Does this make sense?

1 Like

Yes, it sounds great! I may try to develop a schema based on the preCICE adapter, thank you~