Skip to content
Merged
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
File renamed without changes.
21 changes: 11 additions & 10 deletions docs/tulip_data_format.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- [`shield`](#shield)
- [`dielectric`](#dielectric)
- [`open`](#open)
- [`<model>`](#model)
- [`<layers>`](#layers)
- [.tulip.adapted.json file format](#tulipadaptedjson-file-format)
- [Example](#example)
- [`materials` array](#materials-array)
Expand All @@ -21,12 +21,14 @@ Tulip uses three types of file formats:
- `CASE_NAME.tulip.out.json` which is the solver output containing the $L$ and $C$ PUL matrices for shielded domains and the multipolar expansion coefficients for an open domain.

# .tulip.input.json file format
Tulip receives a JSON object as an input with the entries described below. Square brackets indicate that the entry is optional and a default value will be assumed, angle brackets indicate that the entry is mandatory.
Tulip receives a JSON object as an input with the entries described below. Square brackets indicate that the entry is optional and a default value will be assumed, angle brackets indicate that the entry is mandatory.

Unless specified otherwise all units are assumed to be in SI-MKS.

Filename should be in the format `CASE_NAME.tulip.input.json`.

At minimum, the input JSON must include top-level `materials` and `layers` arrays.

## `[adapterOptions]`
It can contain the following entries, as explained in [AdapterOption.h](../src/adapter/AdapterOptions.h) with their corresponding default values. An example is shown below.
```json
Expand All @@ -49,7 +51,7 @@ Driver manages the solver`and generates outputs. Default options can be checked
```

## `<materials>`
These materials are associated with `model` `layers` to define regions with different material properties.
These materials are associated with top-level `layers` to define regions with different material properties.
They are defined by an array of JSON objects with:
- `[name]` a string with a human readable name.
- `<id>` an integer identifier with a unique number.
Expand Down Expand Up @@ -84,12 +86,11 @@ A dielectric is defined with a `[relativePermittivity]` which defaults to `1.0`.
An `open` material serves to specify the computational boundary of the problem. It must intersect every other material layer. If no open boundary is specified for an open problem, one is computed automatically, together with _inner_ and _outer_ regions used to extract the unshielded multiwire coefficients.


## `<model>`
This object can contain the following entries:
+ `<layers>` which is an array which associates the layers present in the `.step` file with the different `materials`. Each layer is specified by:
- `<name>` which must match exactly the name of the corresponding layer within the `.step` file. It must be unique.
- `<id>` which is an integer non-negative unique identifier which will be used to order the results for the calculated PUL matrices.
- `<materialId>` which must match an `id` from a material in the list of `materials`
## `<layers>`
This top-level array associates the layers present in the `.step` file with the different `materials`. Each layer is specified by:
- `<name>` which must match exactly the name of the corresponding layer within the `.step` file. It must be unique.
- `<id>` which is an integer non-negative unique identifier which will be used to order the results for the calculated PUL matrices.
- `<materialId>` which must match an `id` from a material in the list of `materials`


# .tulip.adapted.json file format
Expand Down Expand Up @@ -177,4 +178,4 @@ This object can contain the following entries:

For unshielded-domains stores the parameters needed to reconstruct the field using a multipolar expansion.

It also stores `materialAssociation` information which serves to reconstruct the
It also stores `materialAssociation` information which serves to reconstruct the
33 changes: 33 additions & 0 deletions src/adapter/Adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,38 @@ void validateLayerMaterialIds(const nlohmann::json& inputJson)
}
}

void validateRequiredInputSections(const nlohmann::json& inputJson)
{
const bool hasMaterials =
inputJson.contains("materials") && inputJson["materials"].is_array();
const bool hasLayers = inputJson.contains("layers") && inputJson["layers"].is_array();

if (hasMaterials && hasLayers) {
return;
}

std::string missingSections;
if (!hasMaterials) {
missingSections += "'materials'";
}
if (!hasLayers) {
if (!missingSections.empty()) {
missingSections += " and ";
}
missingSections += "'layers'";
}

std::string message =
"Invalid input JSON: missing required top-level array section(s): " +
missingSections + ".";
if (inputJson.contains("model")) {
message += " Found 'model'; expected top-level 'materials' and 'layers'.";
} else {
message += " Expected top-level 'materials' and 'layers'.";
}
throw std::runtime_error(message);
}

std::vector<std::string> buildAcceptedStepNamesForLayer(
const nlohmann::json& layer,
const std::map<int, std::string>& materialTypeById)
Expand Down Expand Up @@ -799,6 +831,7 @@ void Adapter::initialize(const nlohmann::json& inputJson,
caseName_ = caseName;
inputDir_ = inputDir;

validateRequiredInputSections(inputJson);
validateLayerMaterialIds(inputJson);

adapterOptions_ = parseAdapterOptions(inputJson, std::filesystem::path(inputDir_), caseName_);
Expand Down
9 changes: 9 additions & 0 deletions src/adapter/ShapesClassification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ bool ShapesClassification::isOpenProblem() const

auto roots = nestedGraph.roots();
if (open.size() == 1) return true;
if (roots.empty()) return true;
if (roots.size() > 1) return true;
if (!roots.empty()) {
const auto& root = roots[0];
Expand Down Expand Up @@ -415,7 +416,15 @@ EntityMap ShapesClassification::buildVacuumDomain() {

EntityMap ShapesClassification::buildClosedVacuumDomain() {
const auto roots = nestedGraph.roots();
if (roots.empty()) {
throw std::runtime_error(
"Unable to build closed vacuum domain: no root entity found.");
}
const auto& root = roots[0];
if (!conductors.count(root)) {
throw std::runtime_error(
"Unable to build closed vacuum domain: root entity is not a conductor.");
}
EntityList dom = conductors.at(root);
EntityList toRemove;

Expand Down
50 changes: 50 additions & 0 deletions test/adapter/AdapterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,39 @@ TEST_F(AdapterTest, dielectric_unshielded_pair_fails_if_step_layer_is_not_presen
std::runtime_error);
}

TEST_F(AdapterTest, two_wires_open_fails_if_layers_section_is_missing)
{
const std::string caseName = "two_wires_open";
nlohmann::json inputJson = readInputJsonFromCaseName(caseName);
inputJson.erase("layers");
inputJson["model"] = {{"materials", nlohmann::json::array()}};

try {
Adapter adapter(inputJson, caseName, inputFolderFromCaseName(caseName));
FAIL() << "Expected runtime_error";
} catch (const std::runtime_error& err) {
const std::string message = err.what();
EXPECT_NE(message.find("'layers'"), std::string::npos);
EXPECT_NE(message.find("top-level 'materials' and 'layers'"), std::string::npos);
}
}

TEST_F(AdapterTest, two_wires_open_fails_if_materials_section_is_missing)
{
const std::string caseName = "two_wires_open";
nlohmann::json inputJson = readInputJsonFromCaseName(caseName);
inputJson.erase("materials");

try {
Adapter adapter(inputJson, caseName, inputFolderFromCaseName(caseName));
FAIL() << "Expected runtime_error";
} catch (const std::runtime_error& err) {
const std::string message = err.what();
EXPECT_NE(message.find("'materials'"), std::string::npos);
EXPECT_NE(message.find("top-level 'materials' and 'layers'"), std::string::npos);
}
}

TEST_F(AdapterTest, dielectric_unshielded_pair)
{
const std::string caseName = "dielectric_unshielded_pair";
Expand Down Expand Up @@ -395,3 +428,20 @@ TEST_F(AdapterTest, overlapping_dielectrics_prioritize_higher_relative_permittiv
EXPECT_NEAR(rightHighLeftMass, 4.0, 1e-9);
EXPECT_NEAR(rightHighRightMass, 8.0, 1e-9);
}

TEST_F(AdapterTest, shapes_classification_without_roots_is_treated_as_open_problem)
{
gmsh::clear();
gmsh::model::add("no_roots_case");

const EntityList shapes = {};
const nlohmann::json inputJson = {
{"materials", nlohmann::json::array()},
{"layers", nlohmann::json::array()}
};

ShapesClassification classification(shapes, inputJson);

EXPECT_TRUE(classification.isOpenCase);
EXPECT_TRUE(classification.isOpenProblem());
}
2 changes: 1 addition & 1 deletion testData/agrawal1981/agrawal1981.msh
Original file line number Diff line number Diff line change
Expand Up @@ -29613,7 +29613,7 @@ $Nodes
29597 -0.8995522719202876 1.763043536181375 0
29598 -0.8877177445822432 1.769014368094762 0
29599 -0.8854043705070319 1.781746044666594 0
29600 -0.8972652804878468 1.775827504646807 0
29600 -0.8972652804878468 1.775827504646806 0
29601 -1.310615332880854 0.9476094369748136 0
29602 -1.297944068191723 0.9458983500999457 0
29603 -1.307559396848753 0.9695504897111458 0
Expand Down
Loading