• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

BlueBrain / libsonata / 5186990260

pending completion
5186990260

push

github

mgeplf
implement spec for node population properties for vasculature, astrocyte, endfeet

ie:
    vasculatureFile
    vasculatureMesh
    endfeetMeshesFile
    microdomainsFile
    spineMorphologiesDir

85 of 85 new or added lines in 1 file covered. (100.0%)

1786 of 1854 relevant lines covered (96.33%)

75.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.64
/src/config.cpp
1
/*************************************************************************
2
 * Copyright (C) 2018-2021 Blue Brain Project
3
 *                         Jonas Karlsson <jonas.karlsson@epfl.ch>
4
 *                         Juan Hernando <juan.hernando@epfl.ch>
5
 *
6
 * This file is part of 'libsonata', distributed under the terms
7
 * of the GNU Lesser General Public License version 3.
8
 *
9
 * See top-level COPYING.LESSER and COPYING files for details.
10
 *************************************************************************/
11

12
#include <bbp/sonata/config.h>
13
#include <bbp/sonata/optional.hpp>
14

15
#include <bbp/sonata/optional.hpp>
16
#include <cassert>
17
#include <fstream>
18
#include <memory>
19
#include <regex>
20
#include <set>
21
#include <string>
22

23
#include <fmt/format.h>
24
#include <nlohmann/json.hpp>
25

26
#include "../extlib/filesystem.hpp"
27
#include "population.hpp"
28
#include "utils.h"
29

30
// Add a specialization of adl_serializer to the nlohmann namespace for conversion from/to
31
// nonstd::optional
32
namespace nlohmann {
33
template <typename T>
34
struct adl_serializer<nonstd::optional<T>> {
35
    static void to_json(json& j, const nonstd::optional<T>& opt) {
36
        if (opt == nonstd::nullopt) {
37
            j = nullptr;
38
        } else {
39
            j = *opt;
40
        }
41
    }
42

43
    static void from_json(const json& j, nonstd::optional<T>& opt) {
50✔
44
        if (j.is_null()) {
50✔
45
            opt = nonstd::nullopt;
6✔
46
        } else {
47
            opt = j.get<T>();
44✔
48
        }
49
    }
50✔
50
};
51
}  // namespace nlohmann
52

53
namespace bbp {
54
namespace sonata {
55

56
NLOHMANN_JSON_SERIALIZE_ENUM(CircuitConfig::ConfigStatus,
×
57
                             {{CircuitConfig::ConfigStatus::invalid, nullptr},
58
                              {CircuitConfig::ConfigStatus::partial, "partial"},
59
                              {CircuitConfig::ConfigStatus::complete, "complete"}})
60

61
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Conditions::SpikeLocation,
24✔
62
                             {{SimulationConfig::Conditions::SpikeLocation::invalid, nullptr},
63
                              {SimulationConfig::Conditions::SpikeLocation::soma, "soma"},
64
                              {SimulationConfig::Conditions::SpikeLocation::AIS, "AIS"}})
65

66
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Run::IntegrationMethod,
30✔
67
                             {{SimulationConfig::Run::IntegrationMethod::invalid, nullptr},
68
                              {SimulationConfig::Run::IntegrationMethod::euler, 0},
69
                              {SimulationConfig::Run::IntegrationMethod::nicholson, 1},
70
                              {SimulationConfig::Run::IntegrationMethod::nicholson_ion, 2}})
71

72
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Output::SpikesSortOrder,
16✔
73
                             {{SimulationConfig::Output::SpikesSortOrder::invalid, nullptr},
74
                              {SimulationConfig::Output::SpikesSortOrder::none, "none"},
75
                              {SimulationConfig::Output::SpikesSortOrder::by_id, "by_id"},
76
                              {SimulationConfig::Output::SpikesSortOrder::by_time, "by_time"}})
77

78
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Report::Sections,
68✔
79
                             {{SimulationConfig::Report::Sections::invalid, nullptr},
80
                              {SimulationConfig::Report::Sections::soma, "soma"},
81
                              {SimulationConfig::Report::Sections::axon, "axon"},
82
                              {SimulationConfig::Report::Sections::dend, "dend"},
83
                              {SimulationConfig::Report::Sections::apic, "apic"},
84
                              {SimulationConfig::Report::Sections::all, "all"}})
85
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Report::Type,
114✔
86
                             {{SimulationConfig::Report::Type::invalid, nullptr},
87
                              {SimulationConfig::Report::Type::compartment, "compartment"},
88
                              {SimulationConfig::Report::Type::summation, "summation"},
89
                              {SimulationConfig::Report::Type::synapse, "synapse"}})
90
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Report::Scaling,
12✔
91
                             {{SimulationConfig::Report::Scaling::invalid, nullptr},
92
                              {SimulationConfig::Report::Scaling::none, "none"},
93
                              {SimulationConfig::Report::Scaling::area, "area"}})
94
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Report::Compartments,
12✔
95
                             {{SimulationConfig::Report::Compartments::invalid, nullptr},
96
                              {SimulationConfig::Report::Compartments::center, "center"},
97
                              {SimulationConfig::Report::Compartments::all, "all"}})
98

99
NLOHMANN_JSON_SERIALIZE_ENUM(
690✔
100
    SimulationConfig::InputBase::Module,
101
    {{SimulationConfig::InputBase::Module::invalid, nullptr},
102
     {SimulationConfig::InputBase::Module::linear, "linear"},
103
     {SimulationConfig::InputBase::Module::relative_linear, "relative_linear"},
104
     {SimulationConfig::InputBase::Module::pulse, "pulse"},
105
     {SimulationConfig::InputBase::Module::subthreshold, "subthreshold"},
106
     {SimulationConfig::InputBase::Module::hyperpolarizing, "hyperpolarizing"},
107
     {SimulationConfig::InputBase::Module::synapse_replay, "synapse_replay"},
108
     {SimulationConfig::InputBase::Module::seclamp, "seclamp"},
109
     {SimulationConfig::InputBase::Module::noise, "noise"},
110
     {SimulationConfig::InputBase::Module::shot_noise, "shot_noise"},
111
     {SimulationConfig::InputBase::Module::relative_shot_noise, "relative_shot_noise"},
112
     {SimulationConfig::InputBase::Module::absolute_shot_noise, "absolute_shot_noise"},
113
     {SimulationConfig::InputBase::Module::ornstein_uhlenbeck, "ornstein_uhlenbeck"},
114
     {SimulationConfig::InputBase::Module::relative_ornstein_uhlenbeck,
115
      "relative_ornstein_uhlenbeck"}})
116

117
NLOHMANN_JSON_SERIALIZE_ENUM(
354✔
118
    SimulationConfig::InputBase::InputType,
119
    {{SimulationConfig::InputBase::InputType::invalid, nullptr},
120
     {SimulationConfig::InputBase::InputType::spikes, "spikes"},
121
     {SimulationConfig::InputBase::InputType::extracellular_stimulation,
122
      "extracellular_stimulation"},
123
     {SimulationConfig::InputBase::InputType::current_clamp, "current_clamp"},
124
     {SimulationConfig::InputBase::InputType::voltage_clamp, "voltage_clamp"},
125
     {SimulationConfig::InputBase::InputType::conductance, "conductance"}})
126

127
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::SimulatorType,
24✔
128
                             {{SimulationConfig::SimulatorType::invalid, nullptr},
129
                              {SimulationConfig::SimulatorType::NEURON, "NEURON"},
130
                              {SimulationConfig::SimulatorType::CORENEURON, "CORENEURON"}})
131

132
NLOHMANN_JSON_SERIALIZE_ENUM(
28✔
133
    SimulationConfig::ModificationBase::ModificationType,
134
    {{SimulationConfig::ModificationBase::ModificationType::invalid, nullptr},
135
     {SimulationConfig::ModificationBase::ModificationType::TTX, "TTX"},
136
     {SimulationConfig::ModificationBase::ModificationType::ConfigureAllSections,
137
      "ConfigureAllSections"}})
138

139
namespace {
140
// to be replaced by std::filesystem once C++17 is used
141
namespace fs = ghc::filesystem;
142

143
void raiseOnBiophysicalPopulationErrors(const std::string& population,
26✔
144
                                        const bbp::sonata::NodePopulationProperties& properties) {
145
    if (properties.morphologiesDir.empty() && properties.alternateMorphologyFormats.empty()) {
26✔
146
        throw SonataError(
147
            fmt::format("Node population '{}' is defined as 'biophysical' "
2✔
148
                        "but does not define 'morphologies_dir' or "
149
                        "'alternateMorphologyFormats'",
150
                        population));
6✔
151
    } else if (properties.biophysicalNeuronModelsDir.empty()) {
24✔
152
        throw SonataError(
153
            fmt::format("Node population '{}' is defined as 'biophysical' "
2✔
154
                        "but does not define 'biophysical_neuron_models_dir'",
155
                        population));
6✔
156
    }
157
}
22✔
158

159
void raiseOnVasculaturePopulationErrors(const std::string& population,
×
160
                                        const bbp::sonata::NodePopulationProperties& properties) {
161
    if (!properties.vasculatureFile.has_value() || properties.vasculatureFile.value().empty()) {
×
162
        throw SonataError(
163
            fmt::format("Node population '{}' is defined as 'vasculature' "
×
164
                        "but does not define 'vasculature_file",
165
                        population));
×
166
    } else if (!properties.vasculatureMesh.has_value() ||
×
167
               properties.vasculatureMesh.value().empty()) {
×
168
        throw SonataError(
169
            fmt::format("Node population '{}' is defined as 'vasculature' "
×
170
                        "but does not define 'vasculature_mesh",
171
                        population));
×
172
    }
173
}
×
174

175
void raiseOnAstrocytePopulationErrors(const std::string& population,
×
176
                                      const bbp::sonata::NodePopulationProperties& properties) {
177
    if (!properties.microdomainsFile.has_value() || properties.microdomainsFile.value().empty()) {
×
178
        throw SonataError(
179
            fmt::format("Node population '{}' is defined as 'astrocyte' "
×
180
                        "but does not define 'microdomains_file",
181
                        population));
×
182
    }
183
}
×
184

185
void raiseOnEndfootPopulationErrors(const std::string& population,
×
186
                                    const bbp::sonata::EdgePopulationProperties& properties) {
187
    if (!properties.endfeetMeshesFile.has_value() || properties.endfeetMeshesFile.value().empty()) {
×
188
        throw SonataError(
189
            fmt::format("Node population '{}' is defined as 'endfoot' "
×
190
                        "but does not define 'endfeet_meshes_file",
191
                        population));
×
192
    }
193
}
×
194

195
template <typename PopulationType>
196
PopulationType getPopulationProperties(
30✔
197
    const std::string& populationName,
198
    const std::unordered_map<std::string, PopulationType>& populations) {
199
    auto it = populations.find(populationName);
30✔
200
    if (it == populations.end()) {
30✔
201
        throw SonataError(fmt::format("Could not find population '{}'", populationName));
16✔
202
    }
203

204
    return it->second;
44✔
205
}
206

207
template <typename PopulationType, typename PopulationPropertiesT>
208
PopulationType getPopulation(const std::string& populationName,
8✔
209
                             const std::unordered_map<std::string, PopulationPropertiesT>& src) {
210
    const auto properties = getPopulationProperties(populationName, src);
12✔
211
    return PopulationType(properties.elementsPath, properties.typesPath, populationName);
8✔
212
}
213

214
std::map<std::string, std::string> replaceVariables(std::map<std::string, std::string> variables) {
104✔
215
    constexpr size_t maxIterations = 10;
104✔
216

217
    bool anyChange = true;
104✔
218
    size_t iteration = 0;
104✔
219

220
    while (anyChange) {
248✔
221
        anyChange = false;
148✔
222
        auto variablesCopy = variables;
296✔
223

224
        for (const auto& vI : variables) {
282✔
225
            const auto& vIKey = vI.first;
134✔
226
            const auto& vIValue = vI.second;
134✔
227

228
            for (auto& vJ : variablesCopy) {
416✔
229
                auto& vJValue = vJ.second;
282✔
230
                const auto startPos = vJValue.find(vIKey);
282✔
231

232
                if (startPos != std::string::npos) {
282✔
233
                    vJValue.replace(startPos, vIKey.length(), vIValue);
70✔
234
                    anyChange = true;
70✔
235
                }
236
            }
237
        }
238
        variables = variablesCopy;
148✔
239

240
        if (++iteration == maxIterations) {
148✔
241
            throw SonataError(
242
                "Reached maximum allowed iterations in variable expansion, "
243
                "possibly infinite recursion.");
4✔
244
        }
245
    }
246

247
    return variables;
100✔
248
}
249

250
nlohmann::json expandVariables(const nlohmann::json& json,
100✔
251
                               const std::map<std::string, std::string>& vars) {
252
    auto jsonFlat = json.flatten();
200✔
253

254
    // Expand variables in whole json
255
    for (auto it = jsonFlat.begin(); it != jsonFlat.end(); ++it) {
1,602✔
256
        auto& value = it.value();
1,502✔
257
        if (!value.is_string()) {
1,502✔
258
            continue;
794✔
259
        }
260

261
        auto valueStr = value.get<std::string>();
1,416✔
262

263
        for (const auto& var : vars) {
1,524✔
264
            const auto& varName = var.first;
816✔
265
            const auto& varValue = var.second;
816✔
266
            const auto startPos = valueStr.find(varName);
816✔
267

268
            if (startPos != std::string::npos) {
816✔
269
                valueStr.replace(startPos, varName.length(), varValue);
118✔
270
                value = fs::path(valueStr).lexically_normal();
118✔
271
            }
272
        }
273
    }
274

275
    return jsonFlat.unflatten();
200✔
276
}
277

278
using Variables = std::map<std::string, std::string>;
279

280
Variables readVariables(const nlohmann::json& json) {
106✔
281
    Variables variables;
106✔
282

283
    if (json.find("manifest") == json.end()) {
106✔
284
        return variables;
66✔
285
    }
286

287
    const auto manifest = json["manifest"];
80✔
288

289
    const std::regex regexVariable(R"(\$[a-zA-Z0-9_]*)");
80✔
290

291
    for (auto it = manifest.begin(); it != manifest.end(); ++it) {
96✔
292
        const auto& name = it.key();
58✔
293

294
        if (std::regex_match(name, regexVariable)) {
58✔
295
            variables[name] = it.value();
56✔
296
        } else {
297
            throw SonataError(fmt::format("Invalid variable `{}`", name));
4✔
298
        }
299
    }
300

301
    return variables;
38✔
302
}
303

304
std::string toAbsolute(const fs::path& base, const fs::path& path) {
178✔
305
    const auto absolute = path.is_absolute() ? path : fs::absolute(base / path);
178✔
306
    return absolute.lexically_normal().string();
534✔
307
}
308

309
template <typename Type, typename std::enable_if<std::is_enum<Type>::value>::type* = nullptr>
310
void raiseIfInvalidEnum(const char* name,
230✔
311
                        const Type& buf,
312
                        const std::string& found_value,
313
                        std::true_type /* tag */) {
314
    if (buf == Type::invalid) {
230✔
315
        throw SonataError(fmt::format("Invalid value: '{}' for key '{}'", found_value, name));
32✔
316
    }
317
}
214✔
318

319
template <typename Type>
320
void raiseIfInvalidEnum(const char* /*unused*/,
826✔
321
                        const Type& /*unused*/,
322
                        const std::string& /*unused*/,
323
                        std::false_type /* tag */) {}
826✔
324

325
template <typename Type>
326
void parseMandatory(const nlohmann::json& it,
860✔
327
                    const char* name,
328
                    const std::string& section_name,
329
                    Type& buf) {
330
    const auto element = it.find(name);
860✔
331
    if (element == it.end()) {
860✔
332
        throw SonataError(fmt::format("Could not find '{}' in '{}'", name, section_name));
60✔
333
    }
334
    buf = element->get<Type>();
830✔
335

336
    raiseIfInvalidEnum(name, buf, element->dump(), std::is_enum<Type>());
840✔
337
}
820✔
338

339
template <typename Type>
340
void parseOptional(const nlohmann::json& it,
720✔
341
                   const char* name,
342
                   Type& buf,
343
                   nonstd::optional<Type> default_value = nonstd::nullopt) {
344
    const auto element = it.find(name);
720✔
345
    if (element != it.end()) {
720✔
346
        buf = element->get<Type>();
226✔
347
        raiseIfInvalidEnum(name, buf, element->dump(), std::is_enum<Type>());
232✔
348
    } else if (default_value != nonstd::nullopt) {
494✔
349
        buf = default_value.value();
440✔
350
    }
351
}
714✔
352

353
void parseVariantType(const nlohmann::json& it, variantValueType& var) {
32✔
354
    switch (it.type()) {
32✔
355
    case nlohmann::json::value_t::boolean:
8✔
356
        var = it.get<bool>();
8✔
357
        break;
8✔
358
    case nlohmann::json::value_t::string:
8✔
359
        var = it.get<std::string>();
8✔
360
        break;
8✔
361
    case nlohmann::json::value_t::number_float:
8✔
362
        var = it.get<double>();
8✔
363
        break;
8✔
364
    case nlohmann::json::value_t::number_integer:
8✔
365
    case nlohmann::json::value_t::number_unsigned:
366
        var = it.get<int>();
8✔
367
        break;
8✔
368
    default:
×
369
        throw SonataError("Value type not supported");
×
370
    }
371
}
32✔
372

373
SimulationConfig::Input parseInputModule(const nlohmann::json& valueIt,
68✔
374
                                         const SimulationConfig::InputBase::Module module,
375
                                         const std::string& basePath,
376
                                         const std::string& debugStr) {
377
    using Module = SimulationConfig::InputBase::Module;
378

379
    const auto parseCommon = [&](auto& input) {
68✔
380
        input.module = module;
68✔
381
        parseMandatory(valueIt, "input_type", debugStr, input.inputType);
68✔
382
        parseMandatory(valueIt, "delay", debugStr, input.delay);
66✔
383
        parseMandatory(valueIt, "duration", debugStr, input.duration);
66✔
384
        parseMandatory(valueIt, "node_set", debugStr, input.nodeSet);
66✔
385
    };
134✔
386

387
    switch (module) {
68✔
388
    case Module::linear: {
6✔
389
        SimulationConfig::InputLinear ret;
12✔
390
        parseCommon(ret);
6✔
391
        parseMandatory(valueIt, "amp_start", debugStr, ret.ampStart);
4✔
392
        parseOptional(valueIt, "amp_end", ret.ampEnd, {ret.ampStart});
4✔
393
        return ret;
4✔
394
    }
395
    case Module::relative_linear: {
4✔
396
        SimulationConfig::InputRelativeLinear ret;
8✔
397
        parseCommon(ret);
4✔
398
        parseMandatory(valueIt, "percent_start", debugStr, ret.percentStart);
4✔
399
        parseOptional(valueIt, "percent_end", ret.percentEnd, {ret.percentStart});
4✔
400
        return ret;
4✔
401
    }
402
    case Module::pulse: {
4✔
403
        SimulationConfig::InputPulse ret;
8✔
404
        parseCommon(ret);
4✔
405
        parseMandatory(valueIt, "amp_start", debugStr, ret.ampStart);
4✔
406
        parseMandatory(valueIt, "width", debugStr, ret.width);
4✔
407
        parseMandatory(valueIt, "frequency", debugStr, ret.frequency);
4✔
408
        parseOptional(valueIt, "amp_end", ret.ampEnd, {ret.ampStart});
4✔
409
        return ret;
4✔
410
    }
411
    case Module::subthreshold: {
4✔
412
        SimulationConfig::InputSubthreshold ret;
8✔
413
        parseCommon(ret);
4✔
414
        parseMandatory(valueIt, "percent_less", debugStr, ret.percentLess);
4✔
415
        return ret;
4✔
416
    }
417
    case Module::noise: {
12✔
418
        SimulationConfig::InputNoise ret;
24✔
419
        parseCommon(ret);
12✔
420
        const auto mean = valueIt.find("mean");
12✔
421
        const auto mean_percent = valueIt.find("mean_percent");
12✔
422

423
        if (mean != valueIt.end()) {
12✔
424
            parseOptional(valueIt, "mean", ret.mean);
12✔
425
        }
426

427
        if (mean_percent != valueIt.end()) {
12✔
428
            parseOptional(valueIt, "mean_percent", ret.meanPercent);
6✔
429
        }
430

431
        if (ret.mean.has_value() && ret.meanPercent.has_value()) {
12✔
432
            throw SonataError("Both `mean` or `mean_percent` have values in " + debugStr);
2✔
433
        } else if (!ret.mean.has_value() && !ret.meanPercent.has_value()) {
10✔
434
            throw SonataError("One of `mean` or `mean_percent` need to have a value in " +
4✔
435
                              debugStr);
6✔
436
        }
437

438
        parseOptional(valueIt, "variance", ret.variance);
8✔
439
        return ret;
8✔
440
    }
441
    case Module::shot_noise: {
4✔
442
        SimulationConfig::InputShotNoise ret;
8✔
443
        parseCommon(ret);
4✔
444
        parseMandatory(valueIt, "rise_time", debugStr, ret.riseTime);
4✔
445
        parseMandatory(valueIt, "decay_time", debugStr, ret.decayTime);
4✔
446
        parseOptional(valueIt, "random_seed", ret.randomSeed);
4✔
447
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
448
        parseMandatory(valueIt, "rate", debugStr, ret.rate);
4✔
449
        parseMandatory(valueIt, "amp_mean", debugStr, ret.ampMean);
4✔
450
        parseMandatory(valueIt, "amp_var", debugStr, ret.ampVar);
4✔
451
        return ret;
4✔
452
    }
453
    case Module::relative_shot_noise: {
4✔
454
        SimulationConfig::InputRelativeShotNoise ret;
8✔
455
        parseCommon(ret);
4✔
456

457
        parseMandatory(valueIt, "rise_time", debugStr, ret.riseTime);
4✔
458
        parseMandatory(valueIt, "decay_time", debugStr, ret.decayTime);
4✔
459
        parseOptional(valueIt, "random_seed", ret.randomSeed);
4✔
460
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
461
        parseMandatory(valueIt, "amp_cv", debugStr, ret.ampCv);
4✔
462
        parseMandatory(valueIt, "mean_percent", debugStr, ret.meanPercent);
4✔
463
        parseMandatory(valueIt, "sd_percent", debugStr, ret.sdPercent);
4✔
464
        return ret;
4✔
465
    }
466
    case Module::absolute_shot_noise: {
4✔
467
        SimulationConfig::InputAbsoluteShotNoise ret;
8✔
468
        parseCommon(ret);
4✔
469

470
        parseMandatory(valueIt, "rise_time", debugStr, ret.riseTime);
4✔
471
        parseMandatory(valueIt, "decay_time", debugStr, ret.decayTime);
4✔
472
        parseOptional(valueIt, "random_seed", ret.randomSeed);
4✔
473
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
474
        parseMandatory(valueIt, "amp_cv", debugStr, ret.ampCv);
4✔
475
        parseMandatory(valueIt, "mean", debugStr, ret.mean);
4✔
476
        parseMandatory(valueIt, "sigma", debugStr, ret.sigma);
4✔
477
        return ret;
4✔
478
    }
479
    case Module::hyperpolarizing: {
6✔
480
        SimulationConfig::InputHyperpolarizing ret;
12✔
481
        parseCommon(ret);
6✔
482
        return ret;
6✔
483
    }
484
    case Module::synapse_replay: {
8✔
485
        SimulationConfig::InputSynapseReplay ret;
16✔
486
        parseCommon(ret);
8✔
487
        parseMandatory(valueIt, "spike_file", debugStr, ret.spikeFile);
8✔
488
        parseOptional(valueIt, "source", ret.source);
8✔
489
        ret.spikeFile = toAbsolute(basePath, ret.spikeFile);
8✔
490
        return ret;
8✔
491
    }
492
    case Module::seclamp: {
4✔
493
        SimulationConfig::InputSeclamp ret;
8✔
494
        parseCommon(ret);
4✔
495
        parseMandatory(valueIt, "voltage", debugStr, ret.voltage);
4✔
496
        parseOptional(valueIt, "series_resistance", ret.seriesResistance, {0.01});
4✔
497
        return ret;
4✔
498
    }
499
    case Module::ornstein_uhlenbeck: {
4✔
500
        SimulationConfig::InputOrnsteinUhlenbeck ret;
8✔
501
        parseCommon(ret);
4✔
502
        parseMandatory(valueIt, "tau", debugStr, ret.tau);
4✔
503
        parseOptional(valueIt, "reversal", ret.reversal);
4✔
504
        parseOptional(valueIt, "random_seed", ret.randomSeed);
4✔
505
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
506

507
        parseMandatory(valueIt, "mean", debugStr, ret.mean);
4✔
508
        parseMandatory(valueIt, "sigma", debugStr, ret.sigma);
4✔
509
        return ret;
4✔
510
    }
511
    case Module::relative_ornstein_uhlenbeck: {
4✔
512
        SimulationConfig::InputRelativeOrnsteinUhlenbeck ret;
8✔
513
        parseCommon(ret);
4✔
514
        parseMandatory(valueIt, "tau", debugStr, ret.tau);
4✔
515
        parseOptional(valueIt, "reversal", ret.reversal);
4✔
516
        parseOptional(valueIt, "random_seed", ret.randomSeed);
4✔
517
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
518

519
        parseMandatory(valueIt, "mean_percent", debugStr, ret.meanPercent);
4✔
520
        parseMandatory(valueIt, "sd_percent", debugStr, ret.sdPercent);
4✔
521
        return ret;
4✔
522
    }
523
    default:
×
524
        throw SonataError("Unknown module for the input_type in " + debugStr);
×
525
    }
526
}
527

528
void parseConditionsMechanisms(
4✔
529
    const nlohmann::json& it,
530
    std::unordered_map<std::string, std::unordered_map<std::string, variantValueType>>& buf) {
531
    const auto mechIt = it.find("mechanisms");
4✔
532
    if (mechIt == it.end()) {
4✔
533
        return;
×
534
    }
535
    for (auto& scopeIt : mechIt->items()) {
12✔
536
        std::unordered_map<std::string, variantValueType> map_vars;
8✔
537
        for (auto& varIt : scopeIt.value().items()) {
24✔
538
            variantValueType res_val;
16✔
539
            parseVariantType(varIt.value(), res_val);
16✔
540
            map_vars.insert({varIt.key(), res_val});
16✔
541
        }
542
        buf.insert({scopeIt.key(), map_vars});
8✔
543
    }
544
}
545

546
void parseConditionsModifications(const nlohmann::json& it,
4✔
547
                                  SimulationConfig::ModificationMap& buf) {
548
    const auto sectionIt = it.find("modifications");
4✔
549
    if (sectionIt == it.end()) {
4✔
550
        return;
×
551
    }
552
    for (auto& mIt : sectionIt->items()) {
12✔
553
        const auto valueIt = mIt.value();
16✔
554
        const auto debugStr = fmt::format("modification {}", mIt.key());
16✔
555

556
        SimulationConfig::ModificationBase::ModificationType type;
557
        parseMandatory(valueIt, "type", debugStr, type);
8✔
558

559
        switch (type) {
8✔
560
        case SimulationConfig::ModificationBase::ModificationType::TTX: {
4✔
561
            SimulationConfig::ModificationTTX result;
8✔
562
            result.type = type;
4✔
563
            parseMandatory(valueIt, "node_set", debugStr, result.nodeSet);
4✔
564
            buf[mIt.key()] = result;
4✔
565
            break;
4✔
566
        }
567
        case SimulationConfig::ModificationBase::ModificationType::ConfigureAllSections: {
4✔
568
            SimulationConfig::ModificationConfigureAllSections result;
8✔
569
            result.type = type;
4✔
570
            parseMandatory(valueIt, "node_set", debugStr, result.nodeSet);
4✔
571
            parseMandatory(valueIt, "section_configure", debugStr, result.sectionConfigure);
4✔
572
            buf[mIt.key()] = result;
4✔
573
            break;
4✔
574
        }
575
        default:
×
576
            throw SonataError("Unknown modificationn type in " + debugStr);
×
577
        }
578
    }
579
}
580

581
}  // namespace
582

583
class CircuitConfig::Parser
584
{
585
  public:
586
    Parser(const std::string& contents, const std::string& basePath)
34✔
587
        : _basePath(fs::absolute(fs::path(basePath))) {
46✔
588
        // Parse and expand JSON string
589
        const auto rawJson = nlohmann::json::parse(contents);
68✔
590
        const auto vars = replaceVariables(readVariables(rawJson));
38✔
591
        _json = expandVariables(rawJson, vars);
28✔
592
    }
28✔
593

594
    template <typename T>
595
    T getJSONValue(const nlohmann::json& json,
518✔
596
                   const std::string& key,
597
                   const T& defaultValue = T()) const {
598
        auto it = json.find(key);
518✔
599
        if (it != json.end() && !it->is_null()) {
518✔
600
            return it.value().get<T>();
110✔
601
        }
602

603
        return defaultValue;
408✔
604
    }
605

606
    nonstd::optional<std::string> getOptionalJSONPath(const nlohmann::json& json,
228✔
607
                                                      const std::string& key) const {
608
        auto value = getJSONValue<std::string>(json, key);
456✔
609
        if (!value.empty()) {
228✔
610
            return toAbsolute(_basePath, value);
16✔
611
        }
612

613
        return nonstd::nullopt;
212✔
614
    }
615

616
    std::string getJSONPath(const nlohmann::json& json,
242✔
617
                            const std::string& key,
618
                            const std::string& defaultValue = std::string()) const {
619
        auto value = getJSONValue<std::string>(json, key);
484✔
620
        if (!value.empty()) {
242✔
621
            return toAbsolute(_basePath, value);
82✔
622
        }
623

624
        return defaultValue;
160✔
625
    }
626

627
    ConfigStatus getCircuitConfigStatus() const {
28✔
628
        if (_json.find("metadata") == _json.end()) {
28✔
629
            return ConfigStatus::complete;
28✔
630
        }
631
        const auto& metadata = _json.at("metadata");
×
632
        const auto res = getJSONValue<ConfigStatus>(metadata, "status", {ConfigStatus::complete});
×
633

634
        if (res == ConfigStatus::invalid) {
×
635
            throw SonataError("Invalid value for `metadata::ConfigStatus` in config");
×
636
        }
637

638
        return res;
×
639
    }
640

641
    nlohmann::json getSubNetworkJson(const std::string& prefix,
50✔
642
                                     CircuitConfig::ConfigStatus ConfigStatus) const {
643
        // Fail if no network entry is defined
644
        if (_json.find("networks") == _json.end()) {
50✔
645
            if (ConfigStatus == CircuitConfig::ConfigStatus::complete) {
2✔
646
                throw SonataError("Error parsing config: `networks` not specified");
2✔
647
            } else {
648
                return {};
×
649
            }
650
        }
651

652
        const auto& networks = _json.at("networks");
48✔
653

654
        const std::string component = prefix + "s";
96✔
655
        if (networks.find(component) == networks.end()) {
48✔
656
            if (ConfigStatus == CircuitConfig::ConfigStatus::complete) {
6✔
657
                throw SonataError(
658
                    fmt::format("Error parsing networks config: '{}' not specified", component));
12✔
659
            } else {
660
                return {};
×
661
            }
662
        }
663

664
        return networks.at(component);
42✔
665
    }
666

667
    std::string getNodeSetsPath() const {
28✔
668
        // Retrieve node sets file, if any
669
        if (_json.find("node_sets_file") != _json.end()) {
28✔
670
            return toAbsolute(_basePath, _json["node_sets_file"]);
8✔
671
        }
672

673
        return {};
20✔
674
    }
675

676
    CircuitConfig::Components parseDefaultComponents() const {
18✔
677
        CircuitConfig::Components result;
18✔
678

679
        if (_json.find("components") == _json.end()) {
18✔
680
            return result;
×
681
        }
682

683
        const auto& components = _json.at("components");
18✔
684

685
        result.morphologiesDir = getJSONPath(components, "morphologies_dir");
18✔
686

687
        const auto alternateMorphoDir = components.find("alternate_morphologies");
18✔
688
        if (alternateMorphoDir != components.end()) {
18✔
689
            for (auto it = alternateMorphoDir->begin(); it != alternateMorphoDir->end(); ++it) {
12✔
690
                result.alternateMorphologiesDir[it.key()] = toAbsolute(_basePath, it.value());
6✔
691
            }
692
        }
693

694
        result.biophysicalNeuronModelsDir = getJSONPath(components,
36✔
695
                                                        "biophysical_neuron_models_dir");
18✔
696

697
        result.vasculatureFile = getOptionalJSONPath(components, "vasculature_file");
18✔
698
        result.vasculatureMesh = getOptionalJSONPath(components, "vasculature_mesh");
18✔
699
        result.endfeetMeshesFile = getOptionalJSONPath(components, "endfeetMeshesFile");
18✔
700
        result.microdomainsFile = getOptionalJSONPath(components, "microdomains_file");
18✔
701
        result.spineMorphologiesDir = getOptionalJSONPath(components, "spine_morphologies_dir");
18✔
702

703
        return result;
18✔
704
    }
705

706
    template <typename PopulationPropertiesT>
707
    void updateDefaultProperties(PopulationPropertiesT& component,
48✔
708
                                 const std::string& defaultType,
709
                                 const CircuitConfig::Components& defaultComponents) {
710
        if (component.type.empty()) {
48✔
711
            component.type = defaultType;
40✔
712
        }
713

714
        if (component.alternateMorphologyFormats.empty()) {
48✔
715
            component.alternateMorphologyFormats = defaultComponents.alternateMorphologiesDir;
44✔
716
        }
717

718
        if (component.biophysicalNeuronModelsDir.empty()) {
48✔
719
            component.biophysicalNeuronModelsDir = defaultComponents.biophysicalNeuronModelsDir;
46✔
720
        }
721

722
        if (component.morphologiesDir.empty()) {
48✔
723
            component.morphologiesDir = defaultComponents.morphologiesDir;
44✔
724
        }
725
    }
48✔
726

727
    void updateDefaultNodeProperties(std::unordered_map<std::string, NodePopulationProperties>& map,
18✔
728
                                     const std::string& defaultType,
729
                                     const CircuitConfig::Components& defaultComponents) {
730
        for (auto& entry : map) {
48✔
731
            auto& component = entry.second;
30✔
732
            updateDefaultProperties(component, defaultType, defaultComponents);
30✔
733

734
            if (!component.vasculatureFile) {
30✔
735
                component.vasculatureFile = defaultComponents.vasculatureFile;
30✔
736
            }
737

738
            if (!component.vasculatureMesh) {
30✔
739
                component.vasculatureMesh = defaultComponents.vasculatureMesh;
30✔
740
            }
741

742
            if (!component.microdomainsFile) {
30✔
743
                component.microdomainsFile = defaultComponents.microdomainsFile;
30✔
744
            }
745
            if (!component.spineMorphologiesDir) {
30✔
746
                component.spineMorphologiesDir = defaultComponents.spineMorphologiesDir;
30✔
747
            }
748
        }
749
    }
18✔
750

751
    void updateDefaultEdgeProperties(std::unordered_map<std::string, EdgePopulationProperties>& map,
18✔
752
                                     const std::string& defaultType,
753
                                     const CircuitConfig::Components& defaultComponents) {
754
        for (auto& entry : map) {
36✔
755
            auto& component = entry.second;
18✔
756
            updateDefaultProperties(component, defaultType, defaultComponents);
18✔
757

758
            if (!component.endfeetMeshesFile) {
18✔
759
                component.endfeetMeshesFile = defaultComponents.endfeetMeshesFile;
18✔
760
            }
761
        }
762
    }
18✔
763

764
    template <typename JSON>
765
    std::tuple<std::string, std::string> parseSubNetworks(const std::string& prefix,
32✔
766
                                                          const JSON& value) const {
767
        const std::string elementsFile = prefix + "s_file";
64✔
768
        auto h5File = getJSONPath(value, elementsFile);
64✔
769

770
        if (h5File.empty()) {
32✔
771
            throw SonataError(
772
                fmt::format("'{}' network do not define '{}' entry", prefix, elementsFile));
4✔
773
        }
774

775
        const std::string typesFile = prefix + "_types_file";
60✔
776
        auto csvFile = getJSONPath(value, typesFile);
60✔
777

778
        return {h5File, csvFile};
60✔
779
    }
780

781
    template <typename PopulationProperties, typename Func>
782
    std::unordered_map<std::string, PopulationProperties> parsePopulationProperties(
50✔
783
        const std::string& prefix, CircuitConfig::ConfigStatus status, const Func& parser) const {
784
        const auto& network = getSubNetworkJson(prefix, status);
92✔
785

786
        std::unordered_map<std::string, PopulationProperties> output;
42✔
787

788
        for (const auto& subnetwork : network) {
102✔
789
            std::string elementsPath;
34✔
790
            std::string typesPath;
34✔
791
            std::tie(elementsPath, typesPath) = parseSubNetworks(prefix, subnetwork);
32✔
792

793
            const auto populationsIt = subnetwork.find("populations");
30✔
794
            if (populationsIt == subnetwork.end()) {
30✔
795
                continue;
4✔
796
            }
797

798
            for (auto it = populationsIt->begin(); it != populationsIt->end(); ++it) {
74✔
799
                const auto& popData = it.value();
48✔
800

801
                if (output.find(it.key()) != output.end()) {
48✔
802
                    throw SonataError(fmt::format("Population {} is declared twice", it.key()));
×
803
                }
804

805
                PopulationProperties& popProperties = output[it.key()];
48✔
806

807
                popProperties.elementsPath = elementsPath;
48✔
808
                popProperties.typesPath = typesPath;
48✔
809

810
                popProperties.type = getJSONValue<std::string>(popData, "type");
48✔
811
                popProperties.morphologiesDir = getJSONPath(popData, "morphologies_dir");
48✔
812
                popProperties.biophysicalNeuronModelsDir =
48✔
813
                    getJSONPath(popData, "biophysical_neuron_models_dir");
814

815
                // Overwrite those specified, if any
816
                const auto altMorphoDir = popData.find("alternate_morphologies");
48✔
817
                if (altMorphoDir != popData.end()) {
48✔
818
                    for (auto it = altMorphoDir->begin(); it != altMorphoDir->end(); ++it) {
8✔
819
                        popProperties.alternateMorphologyFormats[it.key()] = toAbsolute(_basePath,
4✔
820
                                                                                        it.value());
4✔
821
                    }
822
                }
823

824
                parser(popProperties, popData);
48✔
825
            }
826
        }
827

828
        return output;
80✔
829
    }
830

831
    std::unordered_map<std::string, NodePopulationProperties> parseNodePopulations(
28✔
832
        CircuitConfig::ConfigStatus status) const {
833
        return parsePopulationProperties<NodePopulationProperties>(
834
            "node",
835
            status,
836
            [&](NodePopulationProperties& popProperties, const nlohmann::json& popData) {
30✔
837
            popProperties.spatialSegmentIndexDir = getJSONPath(popData, "spatial_segment_index_dir");
30✔
838
            popProperties.vasculatureFile = getOptionalJSONPath(popData, "vasculature_file");
30✔
839
            popProperties.vasculatureMesh = getOptionalJSONPath(popData, "vasculature_mesh");
30✔
840
            popProperties.microdomainsFile = getOptionalJSONPath(popData, "microdomains_file");
30✔
841
            popProperties.spineMorphologiesDir = getOptionalJSONPath(popData,
60✔
842
                                                                     "spine_morphologies_dir");
30✔
843
            });
70✔
844
    }
845

846
    std::unordered_map<std::string, EdgePopulationProperties> parseEdgePopulations(
22✔
847
        CircuitConfig::ConfigStatus status) const {
848
        return parsePopulationProperties<EdgePopulationProperties>(
849
            "edge",
850
            status,
851
            [&](EdgePopulationProperties& popProperties, const nlohmann::json& popData) {
18✔
852
            popProperties.spatialSynapseIndexDir = getJSONPath(popData, "spatial_synapse_index_dir");
18✔
853
            popProperties.endfeetMeshesFile = getOptionalJSONPath(popData, "endfeetMeshesFile");
18✔
854
            });
48✔
855
    }
856

857
    std::string getExpandedJSON() const {
28✔
858
        return _json.dump();
28✔
859
    }
860

861
  private:
862
    const fs::path _basePath;
863
    nlohmann::json _json;
864
};
865

866
CircuitConfig::CircuitConfig(const std::string& contents, const std::string& basePath) {
114✔
867
    Parser parser(contents, basePath);
62✔
868

869
    _expandedJSON = parser.getExpandedJSON();
28✔
870
    _status = parser.getCircuitConfigStatus();
28✔
871

872
    _nodeSetsFile = parser.getNodeSetsPath();
28✔
873

874
    _nodePopulationProperties = parser.parseNodePopulations(_status);
28✔
875
    _edgePopulationProperties = parser.parseEdgePopulations(_status);
22✔
876

877
    Components defaultComponents = parser.parseDefaultComponents();
36✔
878

879
    parser.updateDefaultNodeProperties(_nodePopulationProperties, "biophysical", defaultComponents);
18✔
880
    parser.updateDefaultEdgeProperties(_edgePopulationProperties, "chemical", defaultComponents);
18✔
881

882
    if (getCircuitConfigStatus() == ConfigStatus::complete) {
18✔
883
        for (const auto& it : _nodePopulationProperties) {
44✔
884
            const auto& population = it.first;
30✔
885
            const auto& properties = it.second;
30✔
886
            if (properties.type == "biophysical") {
30✔
887
                raiseOnBiophysicalPopulationErrors(population, properties);
26✔
888
            } else if (properties.type == "vasculature") {
4✔
889
                raiseOnVasculaturePopulationErrors(population, properties);
×
890
            } else if (properties.type == "astrocyte") {
4✔
891
                raiseOnAstrocytePopulationErrors(population, properties);
×
892
            }
893
        }
894

895
        for (const auto& it : _edgePopulationProperties) {
32✔
896
            const auto& population = it.first;
18✔
897
            const auto& properties = it.second;
18✔
898
            if (properties.type == "endfoot") {
18✔
899
                raiseOnEndfootPopulationErrors(population, properties);
×
900
            }
901
        }
902
    }
903
}
14✔
904

905
CircuitConfig CircuitConfig::fromFile(const std::string& path) {
14✔
906
    return CircuitConfig(readFile(path), fs::path(path).parent_path());
34✔
907
}
908

909
CircuitConfig::ConfigStatus CircuitConfig::getCircuitConfigStatus() const {
18✔
910
    return _status;
18✔
911
}
912

913
const std::string& CircuitConfig::getNodeSetsPath() const {
10✔
914
    return _nodeSetsFile;
10✔
915
}
916

917
std::set<std::string> CircuitConfig::listNodePopulations() const {
2✔
918
    return getMapKeys(_nodePopulationProperties);
2✔
919
}
920

921
NodePopulation CircuitConfig::getNodePopulation(const std::string& name) const {
4✔
922
    return getPopulation<NodePopulation>(name, _nodePopulationProperties);
4✔
923
}
924

925
std::set<std::string> CircuitConfig::listEdgePopulations() const {
2✔
926
    return getMapKeys(_edgePopulationProperties);
2✔
927
}
928

929
EdgePopulation CircuitConfig::getEdgePopulation(const std::string& name) const {
4✔
930
    return getPopulation<EdgePopulation>(name, _edgePopulationProperties);
4✔
931
}
932

933
NodePopulationProperties CircuitConfig::getNodePopulationProperties(const std::string& name) const {
12✔
934
    return getPopulationProperties(name, _nodePopulationProperties);
12✔
935
}
936

937
EdgePopulationProperties CircuitConfig::getEdgePopulationProperties(const std::string& name) const {
10✔
938
    return getPopulationProperties(name, _edgePopulationProperties);
10✔
939
}
940

941
const std::string& CircuitConfig::getExpandedJSON() const {
4✔
942
    return _expandedJSON;
4✔
943
}
944

945
class SimulationConfig::Parser
946
{
947
  public:
948
    Parser(const std::string& content, const std::string& basePath)
72✔
949
        : _basePath(fs::absolute(fs::path(basePath)).lexically_normal()) {
72✔
950
        // Parse manifest section and expand JSON string
951
        const auto rawJson = nlohmann::json::parse(content);
144✔
952
        const auto vars = replaceVariables(readVariables(rawJson));
72✔
953
        _json = expandVariables(rawJson, vars);
72✔
954
    }
72✔
955

956
    SimulationConfig::Run parseRun() const {
72✔
957
        const auto runIt = _json.find("run");
72✔
958
        if (runIt == _json.end()) {
72✔
959
            throw SonataError("Could not find 'run' section");
2✔
960
        }
961

962
        SimulationConfig::Run result{};
70✔
963
        parseMandatory(*runIt, "tstop", "run", result.tstop);
74✔
964
        parseMandatory(*runIt, "dt", "run", result.dt);
72✔
965
        parseMandatory(*runIt, "random_seed", "run", result.randomSeed);
98✔
966
        parseOptional(*runIt, "spike_threshold", result.spikeThreshold, {-30});
50✔
967
        parseOptional(*runIt,
52✔
968
                      "integration_method",
969
                      result.integrationMethod,
970
                      {Run::IntegrationMethod::euler});
52✔
971
        parseOptional(*runIt, "stimulus_seed", result.stimulusSeed, {0});
48✔
972
        parseOptional(*runIt, "ionchannel_seed", result.ionchannelSeed, {0});
48✔
973
        parseOptional(*runIt, "minis_seed", result.minisSeed, {0});
48✔
974
        parseOptional(*runIt, "synapse_seed", result.synapseSeed, {0});
48✔
975
        return result;
96✔
976
    }
977

978
    SimulationConfig::Output parseOutput() const {
48✔
979
        SimulationConfig::Output result{};
48✔
980

981
        const auto outputIt = _json.find("output");
48✔
982
        if (outputIt == _json.end()) {
48✔
983
            return result;
44✔
984
        }
985
        parseOptional(*outputIt, "output_dir", result.outputDir, {"output"});
4✔
986
        parseOptional(*outputIt, "log_file", result.logFile, {""});
4✔
987
        parseOptional(*outputIt, "spikes_file", result.spikesFile, {"out.h5"});
4✔
988
        parseOptional(*outputIt,
4✔
989
                      "spikes_sort_order",
990
                      result.sortOrder,
4✔
991
                      {Output::SpikesSortOrder::by_time});
4✔
992

993
        result.outputDir = toAbsolute(_basePath, result.outputDir);
4✔
994

995
        return result;
4✔
996
    }
997

998
    SimulationConfig::Conditions parseConditions() const {
48✔
999
        SimulationConfig::Conditions result{};
48✔
1000

1001
        const auto conditionsIt = _json.find("conditions");
48✔
1002
        if (conditionsIt == _json.end()) {
48✔
1003
            return result;
42✔
1004
        }
1005
        parseOptional(*conditionsIt, "celsius", result.celsius, {34.0});
6✔
1006
        parseOptional(*conditionsIt, "v_init", result.vInit, {-80});
6✔
1007
        parseOptional(*conditionsIt,
8✔
1008
                      "spike_location",
1009
                      result.spikeLocation,
6✔
1010
                      {Conditions::SpikeLocation::soma});
8✔
1011
        parseOptional(*conditionsIt, "extracellular_calcium", result.extracellularCalcium);
4✔
1012
        parseOptional(*conditionsIt,
8✔
1013
                      "randomize_gaba_rise_time",
1014
                      result.randomizeGabaRiseTime,
4✔
1015
                      {false});
1016
        parseConditionsMechanisms(*conditionsIt, result.mechanisms);
4✔
1017
        parseConditionsModifications(*conditionsIt, result.modifications);
4✔
1018
        return result;
4✔
1019
    }
1020

1021
    ReportMap parseReports(const SimulationConfig::Output& output) const {
46✔
1022
        ReportMap result;
46✔
1023

1024
        const auto reportsIt = _json.find("reports");
46✔
1025
        if (reportsIt == _json.end()) {
46✔
1026
            return result;
22✔
1027
        }
1028

1029
        for (auto it = reportsIt->begin(); it != reportsIt->end(); ++it) {
40✔
1030
            auto& report = result[it.key()];
36✔
1031
            const auto& valueIt = it.value();
36✔
1032
            const std::string debugStr = "report " + it.key();
72✔
1033

1034
            parseOptional(valueIt, "cells", report.cells, parseNodeSet());
36✔
1035
            parseOptional(valueIt, "sections", report.sections, {Report::Sections::soma});
36✔
1036
            parseMandatory(valueIt, "type", debugStr, report.type);
36✔
1037
            parseOptional(valueIt, "scaling", report.scaling, {Report::Scaling::area});
32✔
1038
            parseOptional(valueIt,
32✔
1039
                          "compartments",
1040
                          report.compartments,
32✔
1041
                          {report.sections == Report::Sections::soma ? Report::Compartments::center
32✔
1042
                                                                     : Report::Compartments::all});
1043
            parseMandatory(valueIt, "variable_name", debugStr, report.variableName);
32✔
1044
            parseOptional(valueIt, "unit", report.unit, {"mV"});
30✔
1045
            parseMandatory(valueIt, "dt", debugStr, report.dt);
30✔
1046
            parseMandatory(valueIt, "start_time", debugStr, report.startTime);
28✔
1047
            parseMandatory(valueIt, "end_time", debugStr, report.endTime);
26✔
1048
            parseOptional(valueIt, "file_name", report.fileName, {it.key() + ".h5"});
24✔
1049
            parseOptional(valueIt, "enabled", report.enabled, {true});
24✔
1050

1051
            // variable names can look like:
1052
            // `v`, or `i_clamp`, or `Foo.bar` but not `..asdf`, or `asdf..` or `asdf.asdf.asdf`
1053
            const char* const varName = R"(\w+(?:\.?\w+)?)";
24✔
1054
            // variable names are separated by `,` with any amount of whitespace separating them
1055
            const std::regex expr(fmt::format(R"({}(?:\s*,\s*{})*)", varName, varName));
48✔
1056
            if (!std::regex_match(report.variableName, expr)) {
24✔
1057
                throw SonataError(fmt::format("Invalid comma separated variable names '{}'",
8✔
1058
                                              report.variableName));
24✔
1059
            }
1060

1061
            const auto extension = fs::path(report.fileName).extension().string();
32✔
1062
            if (extension.empty() || extension != ".h5") {
16✔
1063
                report.fileName += ".h5";
8✔
1064
            }
1065
            report.fileName = toAbsolute(output.outputDir, report.fileName);
16✔
1066
        }
1067

1068
        return result;
4✔
1069
    }
1070

1071
    std::string parseNetwork() const {
34✔
1072
        auto val = _json.value("network", "circuit_config.json");
68✔
1073
        return toAbsolute(_basePath, val);
68✔
1074
    }
1075

1076
    SimulationConfig::SimulatorType parseTargetSimulator() const {
10✔
1077
        SimulationConfig::SimulatorType val;
1078
        parseOptional(_json, "target_simulator", val, {SimulationConfig::SimulatorType::NEURON});
12✔
1079
        return val;
8✔
1080
    }
1081

1082
    std::string parseNodeSetsFile() const noexcept {
8✔
1083
        std::string val;
16✔
1084
        if (_json.contains("node_sets_file")) {
8✔
1085
            val = _json["node_sets_file"];
×
1086
            return toAbsolute(_basePath, val);
×
1087
        } else {
1088
            try {
1089
                const auto circuitFile = parseNetwork();
16✔
1090
                const auto conf = CircuitConfig::fromFile(circuitFile);
12✔
1091
                return conf.getNodeSetsPath();
4✔
1092
            } catch (...) {
4✔
1093
                // Don't throw CircuitConfig exceptions in SimulationConfig and return empty string
1094
                return val;
4✔
1095
            }
1096
        }
1097
    }
1098

1099
    nonstd::optional<std::string> parseNodeSet() const {
44✔
1100
        if (_json.contains("node_set")) {
44✔
1101
            return {_json["node_set"]};
20✔
1102
        } else {
1103
            return nonstd::nullopt;
24✔
1104
        }
1105
    }
1106

1107
    InputMap parseInputs() const {
26✔
1108
        InputMap result;
26✔
1109

1110
        const auto inputsIt = _json.find("inputs");
26✔
1111
        if (inputsIt == _json.end()) {
26✔
1112
            return result;
8✔
1113
        }
1114

1115
        for (auto it = inputsIt->begin(); it != inputsIt->end(); ++it) {
78✔
1116
            const auto& valueIt = it.value();
74✔
1117
            const auto debugStr = fmt::format("input {}", it.key());
148✔
1118

1119
            InputBase::Module module;
1120
            parseMandatory(valueIt, "module", debugStr, module);
74✔
1121

1122
            const auto input = parseInputModule(valueIt, module, _basePath, debugStr);
136✔
1123
            result[it.key()] = input;
62✔
1124

1125
            auto mismatchingModuleInputType = [&it]() {
4✔
1126
                const auto module_name = it->find("module")->get<std::string>();
4✔
1127
                const auto input_type = it->find("input_type")->get<std::string>();
4✔
1128
                throw SonataError(
1129
                    fmt::format("An `input` has module `{}` and input_type `{}` which mismatch",
2✔
1130
                                module_name,
1131
                                input_type));
6✔
1132
            };
62✔
1133

1134
            auto inputType = nonstd::visit([](const auto& v) { return v.inputType; }, input);
124✔
1135
            switch (inputType) {
62✔
1136
            case InputBase::InputType::current_clamp: {
40✔
1137
                if (!(nonstd::holds_alternative<SimulationConfig::InputLinear>(input) ||
76✔
1138
                      nonstd::holds_alternative<SimulationConfig::InputRelativeLinear>(input) ||
36✔
1139
                      nonstd::holds_alternative<SimulationConfig::InputPulse>(input) ||
32✔
1140
                      nonstd::holds_alternative<SimulationConfig::InputSubthreshold>(input) ||
28✔
1141
                      nonstd::holds_alternative<SimulationConfig::InputNoise>(input) ||
24✔
1142
                      nonstd::holds_alternative<SimulationConfig::InputShotNoise>(input) ||
16✔
1143
                      nonstd::holds_alternative<SimulationConfig::InputRelativeShotNoise>(input) ||
12✔
1144
                      nonstd::holds_alternative<SimulationConfig::InputAbsoluteShotNoise>(input) ||
8✔
1145
                      nonstd::holds_alternative<SimulationConfig::InputHyperpolarizing>(input) ||
8✔
1146
                      nonstd::holds_alternative<SimulationConfig::InputOrnsteinUhlenbeck>(input) ||
4✔
1147
                      nonstd::holds_alternative<SimulationConfig::InputRelativeOrnsteinUhlenbeck>(
4✔
1148
                          input))) {
1149
                    mismatchingModuleInputType();
×
1150
                }
1151
            } break;
40✔
1152
            case InputBase::InputType::spikes:
4✔
1153
                if (!nonstd::holds_alternative<SimulationConfig::InputSynapseReplay>(input)) {
4✔
1154
                    mismatchingModuleInputType();
×
1155
                }
1156
                break;
4✔
1157
            case InputBase::InputType::voltage_clamp:
6✔
1158
                if (!nonstd::holds_alternative<SimulationConfig::InputSeclamp>(input)) {
6✔
1159
                    mismatchingModuleInputType();
2✔
1160
                }
1161
                break;
4✔
1162
            case InputBase::InputType::extracellular_stimulation:
4✔
1163
                break;
4✔
1164
            case InputBase::InputType::conductance:
8✔
1165
                if (!(nonstd::holds_alternative<SimulationConfig::InputShotNoise>(input) ||
16✔
1166
                      nonstd::holds_alternative<SimulationConfig::InputRelativeShotNoise>(input) ||
8✔
1167
                      nonstd::holds_alternative<SimulationConfig::InputAbsoluteShotNoise>(input) ||
8✔
1168
                      nonstd::holds_alternative<SimulationConfig::InputOrnsteinUhlenbeck>(input) ||
4✔
1169
                      nonstd::holds_alternative<SimulationConfig::InputRelativeOrnsteinUhlenbeck>(
×
1170
                          input))) {
1171
                    mismatchingModuleInputType();
×
1172
                }
1173
                break;
8✔
1174
            default:
×
1175
                throw SonataError(fmt::format("Unknown input_type in {}", debugStr));
×
1176
            }
1177
        }
1178
        return result;
4✔
1179
    }
1180

1181
    std::vector<ConnectionOverride> parseConnectionOverrides() const {
12✔
1182
        std::vector<ConnectionOverride> result;
12✔
1183

1184
        const auto connIt = _json.find("connection_overrides");
12✔
1185
        // nlohmann::json::flatten().unflatten() converts empty containers to `null`:
1186
        // https://json.nlohmann.me/api/basic_json/unflatten/#notes
1187
        // so we can't tell the difference between {} and []; however, since these are
1188
        // empty, we will assume the intent was to have no connection_overrides and forgo
1189
        // better error reporting
1190
        if (connIt == _json.end() || connIt->is_null()) {
12✔
1191
            return result;
6✔
1192
        }
1193

1194
        if (!connIt->is_array()) {
6✔
1195
            throw SonataError("`connection_overrides` must be an array");
2✔
1196
        }
1197

1198
        result.reserve(connIt->size());
4✔
1199

1200
        for (auto it = connIt->begin(); it != connIt->end(); ++it) {
12✔
1201
            const auto& valueIt = it.value();
8✔
1202
            ConnectionOverride connect;
16✔
1203
            parseMandatory(valueIt, "name", "connection_override", connect.name);
8✔
1204
            const auto debugStr = fmt::format("connection_override {}", connect.name);
8✔
1205
            parseMandatory(valueIt, "source", debugStr, connect.source);
8✔
1206
            parseMandatory(valueIt, "target", debugStr, connect.target);
8✔
1207
            parseOptional(valueIt, "weight", connect.weight);
8✔
1208
            parseOptional(valueIt, "spont_minis", connect.spontMinis);
8✔
1209
            parseOptional(valueIt, "synapse_configure", connect.synapseConfigure);
8✔
1210
            parseOptional(valueIt, "modoverride", connect.modoverride);
8✔
1211
            parseOptional(valueIt, "synapse_delay_override", connect.synapseDelayOverride);
8✔
1212
            parseOptional(valueIt, "delay", connect.delay);
8✔
1213
            parseOptional(valueIt, "neuromodulation_dtc", connect.neuromodulationDtc);
8✔
1214
            parseOptional(valueIt, "neuromodulation_strength", connect.neuromodulationStrength);
8✔
1215

1216
            result.push_back(std::move(connect));
8✔
1217
        }
1218
        return result;
4✔
1219
    }
1220

1221
    std::unordered_map<std::string, std::string> parseMetaData() const {
8✔
1222
        std::unordered_map<std::string, std::string> result;
8✔
1223
        const auto metaIt = _json.find("metadata");
8✔
1224
        if (metaIt == _json.end()) {
8✔
1225
            return result;
4✔
1226
        }
1227
        for (auto& it : metaIt->items()) {
12✔
1228
            result.insert({it.key(), it.value()});
8✔
1229
        }
1230
        return result;
4✔
1231
    }
1232

1233
    std::unordered_map<std::string, variantValueType> parseBetaFeatures() const {
8✔
1234
        std::unordered_map<std::string, variantValueType> result;
8✔
1235
        const auto fIt = _json.find("beta_features");
8✔
1236
        if (fIt == _json.end()) {
8✔
1237
            return result;
4✔
1238
        }
1239
        for (auto& it : fIt->items()) {
20✔
1240
            variantValueType res_val;
16✔
1241
            parseVariantType(it.value(), res_val);
16✔
1242
            result.insert({it.key(), res_val});
16✔
1243
        }
1244
        return result;
4✔
1245
    }
1246

1247
    std::string getExpandedJSON() const {
72✔
1248
        return _json.dump();
72✔
1249
    }
1250

1251
  private:
1252
    const fs::path _basePath;
1253
    nlohmann::json _json;
1254
};
1255

1256
SimulationConfig::SimulationConfig(const std::string& content, const std::string& basePath)
72✔
1257
    : _basePath(fs::absolute(basePath).lexically_normal().string()) {
840✔
1258
    const Parser parser(content, basePath);
136✔
1259
    _expandedJSON = parser.getExpandedJSON();
72✔
1260
    _run = parser.parseRun();
72✔
1261
    _output = parser.parseOutput();
48✔
1262
    _conditions = parser.parseConditions();
48✔
1263
    _reports = parser.parseReports(_output);
46✔
1264
    _network = parser.parseNetwork();
26✔
1265
    _inputs = parser.parseInputs();
26✔
1266
    _connection_overrides = parser.parseConnectionOverrides();
12✔
1267
    _targetSimulator = parser.parseTargetSimulator();
10✔
1268
    _nodeSetsFile = parser.parseNodeSetsFile();
8✔
1269
    _nodeSet = parser.parseNodeSet();
8✔
1270
    _metaData = parser.parseMetaData();
8✔
1271
    _betaFeatures = parser.parseBetaFeatures();
8✔
1272
}
8✔
1273

1274
SimulationConfig SimulationConfig::fromFile(const std::string& path) {
4✔
1275
    return SimulationConfig(readFile(path), fs::path(path).parent_path());
8✔
1276
}
1277

1278
const std::string& SimulationConfig::getBasePath() const noexcept {
2✔
1279
    return _basePath;
2✔
1280
}
1281

1282
const SimulationConfig::Run& SimulationConfig::getRun() const noexcept {
28✔
1283
    return _run;
28✔
1284
}
1285

1286
const SimulationConfig::Output& SimulationConfig::getOutput() const noexcept {
14✔
1287
    return _output;
14✔
1288
}
1289

1290
const SimulationConfig::Conditions& SimulationConfig::getConditions() const noexcept {
28✔
1291
    return _conditions;
28✔
1292
}
1293

1294
const std::string& SimulationConfig::getNetwork() const noexcept {
6✔
1295
    return _network;
6✔
1296
}
1297

1298
std::set<std::string> SimulationConfig::listReportNames() const {
2✔
1299
    return getMapKeys(_reports);
2✔
1300
}
1301

1302
const SimulationConfig::Report& SimulationConfig::getReport(const std::string& name) const {
32✔
1303
    const auto it = _reports.find(name);
32✔
1304
    if (it == _reports.end()) {
32✔
1305
        throw SonataError(
1306
            fmt::format("The report '{}' is not present in the simulation config file", name));
4✔
1307
    }
1308

1309
    return it->second;
30✔
1310
}
1311

1312
std::set<std::string> SimulationConfig::listInputNames() const {
2✔
1313
    return getMapKeys(_inputs);
2✔
1314
}
1315

1316
const SimulationConfig::Input& SimulationConfig::getInput(const std::string& name) const {
30✔
1317
    const auto it = _inputs.find(name);
30✔
1318
    if (it == _inputs.end()) {
30✔
1319
        throw SonataError(
1320
            fmt::format("The input '{}' is not present in the simulation config file", name));
4✔
1321
    }
1322
    return it->second;
28✔
1323
}
1324

1325
const std::vector<SimulationConfig::ConnectionOverride>& SimulationConfig::getConnectionOverrides()
2✔
1326
    const noexcept {
1327
    return _connection_overrides;
2✔
1328
}
1329

1330
const SimulationConfig::SimulatorType& SimulationConfig::getTargetSimulator() const {
4✔
1331
    return _targetSimulator;
4✔
1332
}
1333

1334
const std::string& SimulationConfig::getNodeSetsFile() const noexcept {
4✔
1335
    return _nodeSetsFile;
4✔
1336
}
1337

1338
const nonstd::optional<std::string>& SimulationConfig::getNodeSet() const noexcept {
4✔
1339
    return _nodeSet;
4✔
1340
}
1341

1342
const std::unordered_map<std::string, std::string>& SimulationConfig::getMetaData() const noexcept {
2✔
1343
    return _metaData;
2✔
1344
}
1345

1346
const std::unordered_map<std::string, variantValueType>& SimulationConfig::getBetaFeatures() const
2✔
1347
    noexcept {
1348
    return _betaFeatures;
2✔
1349
}
1350

1351
const std::string& SimulationConfig::getExpandedJSON() const {
2✔
1352
    return _expandedJSON;
2✔
1353
}
1354

1355
std::set<std::string> SimulationConfig::Conditions::listModificationNames() const {
2✔
1356
    return getMapKeys(modifications);
2✔
1357
}
1358

1359
const SimulationConfig::Modification& SimulationConfig::Conditions::getModification(
6✔
1360
    const std::string& name) const {
1361
    const auto it = modifications.find(name);
6✔
1362
    if (it == modifications.end()) {
6✔
1363
        throw SonataError(
1364
            fmt::format("The modification '{}' is not present in the simulation config file",
2✔
1365
                        name));
6✔
1366
    }
1367
    return it->second;
4✔
1368
}
1369

1370
}  // namespace sonata
1371
}  // namespace bbp
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc