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

BlueBrain / libsonata / 11818758916

13 Nov 2024 01:56PM UTC coverage: 93.621%. Remained the same
11818758916

Pull #380

github

mgeplf
Only run the C++ unittests once

* fixes #378
Pull Request #380: Only run the C++ unittests once

1908 of 2038 relevant lines covered (93.62%)

43.86 hits per line

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

93.54
/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

14
#include <bbp/sonata/optional.hpp>
15
#include <regex>
16
#include <set>
17
#include <string>
18

19
#include <fmt/format.h>
20
#include <nlohmann/json.hpp>
21

22
#include "../extlib/filesystem.hpp"
23
#include "utils.h"
24

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

38
    static void from_json(const json& j, nonstd::optional<T>& opt) {
25✔
39
        if (j.is_null()) {
25✔
40
            opt = nonstd::nullopt;
3✔
41
        } else {
42
            opt = j.get<T>();
22✔
43
        }
44
    }
25✔
45
};
46
}  // namespace nlohmann
47

48
namespace bbp {
49
namespace sonata {
50

51
NLOHMANN_JSON_SERIALIZE_ENUM(CircuitConfig::ConfigStatus,
×
52
                             {{CircuitConfig::ConfigStatus::invalid, nullptr},
53
                              {CircuitConfig::ConfigStatus::partial, "partial"},
54
                              {CircuitConfig::ConfigStatus::complete, "complete"}})
55

56
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Conditions::SpikeLocation,
12✔
57
                             {{SimulationConfig::Conditions::SpikeLocation::invalid, nullptr},
58
                              {SimulationConfig::Conditions::SpikeLocation::soma, "soma"},
59
                              {SimulationConfig::Conditions::SpikeLocation::AIS, "AIS"}})
60

61
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Run::IntegrationMethod,
15✔
62
                             {{SimulationConfig::Run::IntegrationMethod::invalid, nullptr},
63
                              {SimulationConfig::Run::IntegrationMethod::euler, 0},
64
                              {SimulationConfig::Run::IntegrationMethod::nicholson, 1},
65
                              {SimulationConfig::Run::IntegrationMethod::nicholson_ion, 2}})
66

67
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Output::SpikesSortOrder,
8✔
68
                             {{SimulationConfig::Output::SpikesSortOrder::invalid, nullptr},
69
                              {SimulationConfig::Output::SpikesSortOrder::none, "none"},
70
                              {SimulationConfig::Output::SpikesSortOrder::by_id, "by_id"},
71
                              {SimulationConfig::Output::SpikesSortOrder::by_time, "by_time"}})
72

73
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Report::Sections,
34✔
74
                             {{SimulationConfig::Report::Sections::invalid, nullptr},
75
                              {SimulationConfig::Report::Sections::soma, "soma"},
76
                              {SimulationConfig::Report::Sections::axon, "axon"},
77
                              {SimulationConfig::Report::Sections::dend, "dend"},
78
                              {SimulationConfig::Report::Sections::apic, "apic"},
79
                              {SimulationConfig::Report::Sections::all, "all"}})
80
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Report::Type,
70✔
81
                             {{SimulationConfig::Report::Type::invalid, nullptr},
82
                              {SimulationConfig::Report::Type::compartment, "compartment"},
83
                              {SimulationConfig::Report::Type::lfp, "lfp"},
84
                              {SimulationConfig::Report::Type::summation, "summation"},
85
                              {SimulationConfig::Report::Type::synapse, "synapse"}})
86
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Report::Scaling,
6✔
87
                             {{SimulationConfig::Report::Scaling::invalid, nullptr},
88
                              {SimulationConfig::Report::Scaling::none, "none"},
89
                              {SimulationConfig::Report::Scaling::area, "area"}})
90
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Report::Compartments,
6✔
91
                             {{SimulationConfig::Report::Compartments::invalid, nullptr},
92
                              {SimulationConfig::Report::Compartments::center, "center"},
93
                              {SimulationConfig::Report::Compartments::all, "all"}})
94

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

114
NLOHMANN_JSON_SERIALIZE_ENUM(
200✔
115
    SimulationConfig::InputBase::InputType,
116
    {{SimulationConfig::InputBase::InputType::invalid, nullptr},
117
     {SimulationConfig::InputBase::InputType::spikes, "spikes"},
118
     {SimulationConfig::InputBase::InputType::extracellular_stimulation,
119
      "extracellular_stimulation"},
120
     {SimulationConfig::InputBase::InputType::current_clamp, "current_clamp"},
121
     {SimulationConfig::InputBase::InputType::voltage_clamp, "voltage_clamp"},
122
     {SimulationConfig::InputBase::InputType::conductance, "conductance"}})
123

124
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::SimulatorType,
12✔
125
                             {{SimulationConfig::SimulatorType::invalid, nullptr},
126
                              {SimulationConfig::SimulatorType::NEURON, "NEURON"},
127
                              {SimulationConfig::SimulatorType::CORENEURON, "CORENEURON"}})
128

129
NLOHMANN_JSON_SERIALIZE_ENUM(
14✔
130
    SimulationConfig::ModificationBase::ModificationType,
131
    {{SimulationConfig::ModificationBase::ModificationType::invalid, nullptr},
132
     {SimulationConfig::ModificationBase::ModificationType::TTX, "TTX"},
133
     {SimulationConfig::ModificationBase::ModificationType::ConfigureAllSections,
134
      "ConfigureAllSections"}})
135

136
// { in C++14; one has to declare static constexpr members; this can go away in c++17
137
#define D(name) decltype(SimulationConfig::name) constexpr SimulationConfig::name;
138
D(Output::DEFAULT_outputDir)
139
D(Conditions::DEFAULT_randomizeGabaRiseTime)
140
D(Output::DEFAULT_logFile)
141
D(Run::DEFAULT_stimulusSeed)
142
D(Conditions::DEFAULT_spikeLocation)
143
D(Run::DEFAULT_ionchannelSeed)
144
D(Output::DEFAULT_sortOrder)
145
D(Run::DEFAULT_IntegrationMethod)
146
D(Conditions::DEFAULT_vInit)
147
D(Run::DEFAULT_minisSeed)
148
D(Output::DEFAULT_spikesFile)
149
D(Conditions::DEFAULT_celsius)
150
D(Run::DEFAULT_synapseSeed)
151
D(Run::DEFAULT_spikeThreshold)
152
#undef D
153
// }
154

155
namespace {
156
// to be replaced by std::filesystem once C++17 is used
157
namespace fs = ghc::filesystem;
158

159
void raiseOnBiophysicalPopulationErrors(const std::string& population,
13✔
160
                                        const bbp::sonata::NodePopulationProperties& properties) {
161
    if (properties.morphologiesDir.empty() && properties.alternateMorphologyFormats.empty()) {
13✔
162
        throw SonataError(
163
            fmt::format("Node population '{}' is defined as 'biophysical' "
2✔
164
                        "but does not define 'morphologies_dir' or "
165
                        "'alternateMorphologyFormats'",
166
                        population));
3✔
167
    } else if (properties.biophysicalNeuronModelsDir.empty()) {
12✔
168
        throw SonataError(
169
            fmt::format("Node population '{}' is defined as 'biophysical' "
2✔
170
                        "but does not define 'biophysical_neuron_models_dir'",
171
                        population));
3✔
172
    }
173
}
11✔
174

175
void raiseOnVasculaturePopulationErrors(const std::string& population,
×
176
                                        const bbp::sonata::NodePopulationProperties& properties) {
177
    if (!properties.vasculatureFile.has_value() || properties.vasculatureFile.value().empty()) {
×
178
        throw SonataError(
179
            fmt::format("Node population '{}' is defined as 'vasculature' "
×
180
                        "but does not define 'vasculature_file",
181
                        population));
×
182
    } else if (!properties.vasculatureMesh.has_value() ||
×
183
               properties.vasculatureMesh.value().empty()) {
×
184
        throw SonataError(
185
            fmt::format("Node population '{}' is defined as 'vasculature' "
×
186
                        "but does not define 'vasculature_mesh",
187
                        population));
×
188
    }
189
}
×
190

191
void raiseOnAstrocytePopulationErrors(const std::string& population,
×
192
                                      const bbp::sonata::NodePopulationProperties& properties) {
193
    if (!properties.microdomainsFile.has_value() || properties.microdomainsFile.value().empty()) {
×
194
        throw SonataError(
195
            fmt::format("Node population '{}' is defined as 'astrocyte' "
×
196
                        "but does not define 'microdomains_file",
197
                        population));
×
198
    }
199
}
×
200

201
void raiseOnEndfootPopulationErrors(const std::string& population,
×
202
                                    const bbp::sonata::EdgePopulationProperties& properties) {
203
    if (!properties.endfeetMeshesFile.has_value() || properties.endfeetMeshesFile.value().empty()) {
×
204
        throw SonataError(
205
            fmt::format("Node population '{}' is defined as 'endfoot' "
×
206
                        "but does not define 'endfeet_meshes_file",
207
                        population));
×
208
    }
209
}
×
210

211
template <typename PopulationType>
212
PopulationType getPopulationProperties(
15✔
213
    const std::string& populationName,
214
    const std::unordered_map<std::string, PopulationType>& populations) {
215
    auto it = populations.find(populationName);
15✔
216
    if (it == populations.end()) {
15✔
217
        throw SonataError(fmt::format("Could not find population '{}'", populationName));
8✔
218
    }
219

220
    return it->second;
22✔
221
}
222

223
template <typename PopulationType, typename PopulationPropertiesT>
224
PopulationType getPopulation(const std::string& populationName,
4✔
225
                             const std::unordered_map<std::string, PopulationPropertiesT>& src,
226
                             const Hdf5Reader& hdf5_reader) {
227
    const auto properties = getPopulationProperties(populationName, src);
6✔
228
    return PopulationType(properties.elementsPath,
229
                          properties.typesPath,
230
                          populationName,
231
                          hdf5_reader);
4✔
232
}
233

234
std::map<std::string, std::string> replaceVariables(std::map<std::string, std::string> variables) {
53✔
235
    constexpr size_t maxIterations = 10;
53✔
236

237
    bool anyChange = true;
53✔
238
    size_t iteration = 0;
53✔
239

240
    while (anyChange) {
126✔
241
        anyChange = false;
75✔
242
        auto variablesCopy = variables;
150✔
243

244
        for (const auto& vI : variables) {
144✔
245
            const auto& vIKey = vI.first;
69✔
246
            const auto& vIValue = vI.second;
69✔
247

248
            for (auto& vJ : variablesCopy) {
216✔
249
                auto& vJValue = vJ.second;
147✔
250
                const auto startPos = vJValue.find(vIKey);
147✔
251

252
                if (startPos != std::string::npos) {
147✔
253
                    vJValue.replace(startPos, vIKey.length(), vIValue);
35✔
254
                    anyChange = true;
35✔
255
                }
256
            }
257
        }
258
        variables = variablesCopy;
75✔
259

260
        if (++iteration == maxIterations) {
75✔
261
            throw SonataError(
262
                "Reached maximum allowed iterations in variable expansion, "
263
                "possibly infinite recursion.");
2✔
264
        }
265
    }
266

267
    return variables;
51✔
268
}
269

270
nlohmann::json expandVariables(const nlohmann::json& json,
51✔
271
                               const std::map<std::string, std::string>& vars) {
272
    auto jsonFlat = json.flatten();
102✔
273

274
    // Expand variables in whole json
275
    for (auto it = jsonFlat.begin(); it != jsonFlat.end(); ++it) {
879✔
276
        auto& value = it.value();
828✔
277
        if (!value.is_string()) {
828✔
278
            continue;
438✔
279
        }
280

281
        auto valueStr = value.get<std::string>();
780✔
282

283
        for (const auto& var : vars) {
1,060✔
284
            const auto& varName = var.first;
670✔
285
            const auto& varValue = var.second;
670✔
286
            const auto startPos = valueStr.find(varName);
670✔
287

288
            if (startPos != std::string::npos) {
670✔
289
                valueStr.replace(startPos, varName.length(), varValue);
69✔
290
                value = fs::path(valueStr).lexically_normal();
69✔
291
            }
292
        }
293
    }
294

295
    return jsonFlat.unflatten();
102✔
296
}
297

298
using Variables = std::map<std::string, std::string>;
299

300
Variables readVariables(const nlohmann::json& json) {
54✔
301
    Variables variables;
54✔
302

303
    if (json.find("manifest") == json.end()) {
54✔
304
        return variables;
34✔
305
    }
306

307
    const auto& manifest = json["manifest"];
20✔
308

309
    const std::regex regexVariable(R"(\$[a-zA-Z0-9_]*)");
40✔
310

311
    for (auto it = manifest.begin(); it != manifest.end(); ++it) {
50✔
312
        const auto& name = it.key();
31✔
313

314
        if (std::regex_match(name, regexVariable)) {
31✔
315
            variables[name] = it.value();
30✔
316
        } else {
317
            throw SonataError(fmt::format("Invalid variable `{}`", name));
2✔
318
        }
319
    }
320

321
    return variables;
19✔
322
}
323

324
std::string toAbsolute(const fs::path& base, const fs::path& path) {
103✔
325
    const auto absolute = path.is_absolute() ? path : fs::absolute(base / path);
103✔
326
    return absolute.lexically_normal().string();
309✔
327
}
328

329
template <typename Type, std::enable_if_t<std::is_enum<Type>::value>* = nullptr>
330
void raiseIfInvalidEnum(const char* name,
127✔
331
                        const Type& buf,
332
                        const std::string& found_value,
333
                        std::true_type /* tag */) {
334
    if (buf == Type::invalid) {
127✔
335
        throw SonataError(fmt::format("Invalid value: '{}' for key '{}'", found_value, name));
16✔
336
    }
337
}
119✔
338

339
template <typename Type>
340
void raiseIfInvalidEnum(const char* /*unused*/,
464✔
341
                        const Type& /*unused*/,
342
                        const std::string& /*unused*/,
343
                        std::false_type /* tag */) {}
464✔
344

345
template <typename Type>
346
void parseMandatory(const nlohmann::json& it,
477✔
347
                    const char* name,
348
                    const std::string& section_name,
349
                    Type& buf) {
350
    const auto element = it.find(name);
477✔
351
    if (element == it.end()) {
477✔
352
        throw SonataError(fmt::format("Could not find '{}' in '{}'", name, section_name));
30✔
353
    }
354
    buf = element->get<Type>();
462✔
355

356
    raiseIfInvalidEnum(name, buf, element->dump(), std::is_enum<Type>());
467✔
357
}
457✔
358

359
template <typename Type>
360
void parseOptional(const nlohmann::json& it,
442✔
361
                   const char* name,
362
                   Type& buf,
363
                   nonstd::optional<Type> default_value = nonstd::nullopt) {
364
    const auto element = it.find(name);
442✔
365
    if (element != it.end()) {
442✔
366
        buf = element->get<Type>();
129✔
367
        raiseIfInvalidEnum(name, buf, element->dump(), std::is_enum<Type>());
132✔
368
    } else if (default_value != nonstd::nullopt) {
313✔
369
        buf = default_value.value();
290✔
370
    }
371
}
439✔
372

373
void parseVariantType(const nlohmann::json& it, variantValueType& var) {
24✔
374
    switch (it.type()) {
24✔
375
    case nlohmann::json::value_t::boolean:
6✔
376
        var = it.get<bool>();
6✔
377
        break;
6✔
378
    case nlohmann::json::value_t::string:
6✔
379
        var = it.get<std::string>();
6✔
380
        break;
6✔
381
    case nlohmann::json::value_t::number_float:
6✔
382
        var = it.get<double>();
6✔
383
        break;
6✔
384
    case nlohmann::json::value_t::number_integer:
6✔
385
    case nlohmann::json::value_t::number_unsigned:
386
        var = it.get<int>();
6✔
387
        break;
6✔
388
    default:
×
389
        throw SonataError("Value type not supported");
×
390
    }
391
}
24✔
392

393
SimulationConfig::Input parseInputModule(const nlohmann::json& valueIt,
39✔
394
                                         const SimulationConfig::InputBase::Module module,
395
                                         const std::string& basePath,
396
                                         const std::string& debugStr) {
397
    using Module = SimulationConfig::InputBase::Module;
398

399
    const auto parseCommon = [&](auto& input) {
39✔
400
        input.module = module;
39✔
401
        parseMandatory(valueIt, "input_type", debugStr, input.inputType);
39✔
402
        parseMandatory(valueIt, "delay", debugStr, input.delay);
38✔
403
        parseMandatory(valueIt, "duration", debugStr, input.duration);
38✔
404
        parseMandatory(valueIt, "node_set", debugStr, input.nodeSet);
38✔
405
    };
77✔
406

407
    switch (module) {
39✔
408
    case Module::linear: {
3✔
409
        SimulationConfig::InputLinear ret;
6✔
410
        parseCommon(ret);
3✔
411
        parseMandatory(valueIt, "amp_start", debugStr, ret.ampStart);
2✔
412
        parseOptional(valueIt, "amp_end", ret.ampEnd, {ret.ampStart});
2✔
413
        parseOptional(valueIt,
2✔
414
                      "represents_physical_electrode",
415
                      ret.representsPhysicalElectrode,
416
                      {false});
417
        return ret;
2✔
418
    }
419
    case Module::relative_linear: {
2✔
420
        SimulationConfig::InputRelativeLinear ret;
4✔
421
        parseCommon(ret);
2✔
422
        parseMandatory(valueIt, "percent_start", debugStr, ret.percentStart);
2✔
423
        parseOptional(valueIt, "percent_end", ret.percentEnd, {ret.percentStart});
2✔
424
        parseOptional(valueIt,
2✔
425
                      "represents_physical_electrode",
426
                      ret.representsPhysicalElectrode,
427
                      {false});
428
        return ret;
2✔
429
    }
430
    case Module::pulse: {
2✔
431
        SimulationConfig::InputPulse ret;
4✔
432
        parseCommon(ret);
2✔
433
        parseMandatory(valueIt, "amp_start", debugStr, ret.ampStart);
2✔
434
        parseMandatory(valueIt, "width", debugStr, ret.width);
2✔
435
        parseMandatory(valueIt, "frequency", debugStr, ret.frequency);
2✔
436
        parseOptional(valueIt,
2✔
437
                      "represents_physical_electrode",
438
                      ret.representsPhysicalElectrode,
439
                      {false});
440
        return ret;
2✔
441
    }
442
    case Module::sinusoidal: {
4✔
443
        SimulationConfig::InputSinusoidal ret;
8✔
444
        parseCommon(ret);
4✔
445
        parseMandatory(valueIt, "amp_start", debugStr, ret.ampStart);
4✔
446
        parseMandatory(valueIt, "frequency", debugStr, ret.frequency);
4✔
447
        parseOptional(valueIt, "dt", ret.dt, {0.025});
4✔
448
        parseOptional(valueIt,
4✔
449
                      "represents_physical_electrode",
450
                      ret.representsPhysicalElectrode,
451
                      {false});
452
        return ret;
4✔
453
    }
454
    case Module::subthreshold: {
2✔
455
        SimulationConfig::InputSubthreshold ret;
4✔
456
        parseCommon(ret);
2✔
457
        parseMandatory(valueIt, "percent_less", debugStr, ret.percentLess);
2✔
458
        parseOptional(valueIt,
2✔
459
                      "represents_physical_electrode",
460
                      ret.representsPhysicalElectrode,
461
                      {false});
462
        return ret;
2✔
463
    }
464
    case Module::noise: {
6✔
465
        SimulationConfig::InputNoise ret;
12✔
466
        parseCommon(ret);
6✔
467
        const auto mean = valueIt.find("mean");
6✔
468
        const auto mean_percent = valueIt.find("mean_percent");
6✔
469

470
        if (mean != valueIt.end()) {
6✔
471
            parseOptional(valueIt, "mean", ret.mean);
6✔
472
        }
473

474
        if (mean_percent != valueIt.end()) {
6✔
475
            parseOptional(valueIt, "mean_percent", ret.meanPercent);
3✔
476
        }
477

478
        if (ret.mean.has_value() && ret.meanPercent.has_value()) {
6✔
479
            throw SonataError("Both `mean` or `mean_percent` have values in " + debugStr);
1✔
480
        } else if (!ret.mean.has_value() && !ret.meanPercent.has_value()) {
5✔
481
            throw SonataError("One of `mean` or `mean_percent` need to have a value in " +
2✔
482
                              debugStr);
3✔
483
        }
484

485
        parseOptional(valueIt, "variance", ret.variance);
4✔
486
        parseOptional(valueIt,
4✔
487
                      "represents_physical_electrode",
488
                      ret.representsPhysicalElectrode,
489
                      {false});
490
        return ret;
4✔
491
    }
492
    case Module::shot_noise: {
2✔
493
        SimulationConfig::InputShotNoise ret;
4✔
494
        parseCommon(ret);
2✔
495
        parseMandatory(valueIt, "rise_time", debugStr, ret.riseTime);
2✔
496
        parseMandatory(valueIt, "decay_time", debugStr, ret.decayTime);
2✔
497
        parseOptional(valueIt, "random_seed", ret.randomSeed);
2✔
498
        parseOptional(valueIt, "reversal", ret.reversal, {0.0});
2✔
499
        parseOptional(valueIt, "dt", ret.dt, {0.25});
2✔
500
        parseOptional(valueIt,
2✔
501
                      "represents_physical_electrode",
502
                      ret.representsPhysicalElectrode,
503
                      {false});
504
        parseMandatory(valueIt, "rate", debugStr, ret.rate);
2✔
505
        parseMandatory(valueIt, "amp_mean", debugStr, ret.ampMean);
2✔
506
        parseMandatory(valueIt, "amp_var", debugStr, ret.ampVar);
2✔
507
        return ret;
2✔
508
    }
509
    case Module::relative_shot_noise: {
2✔
510
        SimulationConfig::InputRelativeShotNoise ret;
4✔
511
        parseCommon(ret);
2✔
512

513
        parseMandatory(valueIt, "rise_time", debugStr, ret.riseTime);
2✔
514
        parseMandatory(valueIt, "decay_time", debugStr, ret.decayTime);
2✔
515
        parseOptional(valueIt, "random_seed", ret.randomSeed);
2✔
516
        parseOptional(valueIt, "reversal", ret.reversal, {0.0});
2✔
517
        parseOptional(valueIt, "dt", ret.dt, {0.25});
2✔
518
        parseOptional(valueIt,
2✔
519
                      "represents_physical_electrode",
520
                      ret.representsPhysicalElectrode,
521
                      {false});
522
        parseMandatory(valueIt, "mean_percent", debugStr, ret.meanPercent);
2✔
523
        parseMandatory(valueIt, "sd_percent", debugStr, ret.sdPercent);
2✔
524
        parseOptional(valueIt, "relative_skew", ret.relativeSkew, {0.5});
2✔
525
        return ret;
2✔
526
    }
527
    case Module::absolute_shot_noise: {
2✔
528
        SimulationConfig::InputAbsoluteShotNoise ret;
4✔
529
        parseCommon(ret);
2✔
530

531
        parseMandatory(valueIt, "rise_time", debugStr, ret.riseTime);
2✔
532
        parseMandatory(valueIt, "decay_time", debugStr, ret.decayTime);
2✔
533
        parseOptional(valueIt, "random_seed", ret.randomSeed);
2✔
534
        parseOptional(valueIt, "reversal", ret.reversal, {0.0});
2✔
535
        parseOptional(valueIt, "dt", ret.dt, {0.25});
2✔
536
        parseOptional(valueIt,
2✔
537
                      "represents_physical_electrode",
538
                      ret.representsPhysicalElectrode,
539
                      {false});
540
        parseMandatory(valueIt, "mean", debugStr, ret.mean);
2✔
541
        parseMandatory(valueIt, "sigma", debugStr, ret.sigma);
2✔
542
        parseOptional(valueIt, "relative_skew", ret.relativeSkew, {0.5});
2✔
543
        return ret;
2✔
544
    }
545
    case Module::hyperpolarizing: {
3✔
546
        SimulationConfig::InputHyperpolarizing ret;
6✔
547
        parseCommon(ret);
3✔
548
        parseOptional(valueIt,
3✔
549
                      "represents_physical_electrode",
550
                      ret.representsPhysicalElectrode,
551
                      {false});
552
        return ret;
3✔
553
    }
554
    case Module::synapse_replay: {
5✔
555
        SimulationConfig::InputSynapseReplay ret;
10✔
556
        parseCommon(ret);
5✔
557
        parseMandatory(valueIt, "spike_file", debugStr, ret.spikeFile);
5✔
558
        ret.spikeFile = toAbsolute(basePath, ret.spikeFile);
5✔
559
        const auto extension = fs::path(ret.spikeFile).extension().string();
15✔
560
        if (extension != ".h5") {
5✔
561
            throw SonataError("Replay spike_file should be a SONATA h5 file");
1✔
562
        }
563
        return ret;
4✔
564
    }
565
    case Module::seclamp: {
2✔
566
        SimulationConfig::InputSeclamp ret;
4✔
567
        parseCommon(ret);
2✔
568
        parseMandatory(valueIt, "voltage", debugStr, ret.voltage);
2✔
569
        parseOptional(valueIt, "series_resistance", ret.seriesResistance, {0.01});
2✔
570
        return ret;
2✔
571
    }
572
    case Module::ornstein_uhlenbeck: {
2✔
573
        SimulationConfig::InputOrnsteinUhlenbeck ret;
4✔
574
        parseCommon(ret);
2✔
575
        parseMandatory(valueIt, "tau", debugStr, ret.tau);
2✔
576
        parseOptional(valueIt, "reversal", ret.reversal, {0.0});
2✔
577
        parseOptional(valueIt, "random_seed", ret.randomSeed);
2✔
578
        parseOptional(valueIt, "dt", ret.dt, {0.25});
2✔
579
        parseOptional(valueIt,
2✔
580
                      "represents_physical_electrode",
581
                      ret.representsPhysicalElectrode,
582
                      {false});
583

584
        parseMandatory(valueIt, "mean", debugStr, ret.mean);
2✔
585
        parseMandatory(valueIt, "sigma", debugStr, ret.sigma);
2✔
586
        return ret;
2✔
587
    }
588
    case Module::relative_ornstein_uhlenbeck: {
2✔
589
        SimulationConfig::InputRelativeOrnsteinUhlenbeck ret;
4✔
590
        parseCommon(ret);
2✔
591
        parseMandatory(valueIt, "tau", debugStr, ret.tau);
2✔
592
        parseOptional(valueIt, "reversal", ret.reversal, {0.0});
2✔
593
        parseOptional(valueIt, "random_seed", ret.randomSeed);
2✔
594
        parseOptional(valueIt, "dt", ret.dt, {0.25});
2✔
595
        parseOptional(valueIt,
2✔
596
                      "represents_physical_electrode",
597
                      ret.representsPhysicalElectrode,
598
                      {false});
599

600
        parseMandatory(valueIt, "mean_percent", debugStr, ret.meanPercent);
2✔
601
        parseMandatory(valueIt, "sd_percent", debugStr, ret.sdPercent);
2✔
602
        return ret;
2✔
603
    }
604
    default:
×
605
        throw SonataError("Unknown module for the input_type in " + debugStr);
×
606
    }
607
}
608

609
void parseConditionsMechanisms(
2✔
610
    const nlohmann::json& it,
611
    std::unordered_map<std::string, std::unordered_map<std::string, variantValueType>>& buf) {
612
    const auto mechIt = it.find("mechanisms");
2✔
613
    if (mechIt == it.end()) {
2✔
614
        return;
×
615
    }
616
    for (auto& scopeIt : mechIt->items()) {
6✔
617
        std::unordered_map<std::string, variantValueType> map_vars;
4✔
618
        for (auto& varIt : scopeIt.value().items()) {
12✔
619
            variantValueType res_val;
8✔
620
            parseVariantType(varIt.value(), res_val);
8✔
621
            map_vars.insert({varIt.key(), res_val});
8✔
622
        }
623
        buf.insert({scopeIt.key(), map_vars});
4✔
624
    }
625
}
626

627
void parseConditionsModifications(const nlohmann::json& it,
2✔
628
                                  std::vector<SimulationConfig::Modification>& buf) {
629
    const auto sectionIt = it.find("modifications");
2✔
630
    if (sectionIt == it.end() || sectionIt->is_null()) {
2✔
631
        return;
×
632
    }
633
    if (!sectionIt->is_array()) {
2✔
634
        throw SonataError("`modifications` must be an array");
×
635
    }
636
    buf.reserve(sectionIt->size());
2✔
637

638
    for (auto& mIt : sectionIt->items()) {
6✔
639
        const auto valueIt = mIt.value();
8✔
640
        const auto debugStr = fmt::format("modification {}", mIt.key());
12✔
641

642
        SimulationConfig::ModificationBase::ModificationType type;
643
        parseMandatory(valueIt, "type", debugStr, type);
4✔
644

645
        switch (type) {
4✔
646
        case SimulationConfig::ModificationBase::ModificationType::TTX: {
2✔
647
            SimulationConfig::ModificationTTX result;
4✔
648
            result.type = type;
2✔
649
            parseMandatory(valueIt, "name", debugStr, result.name);
2✔
650
            parseMandatory(valueIt, "node_set", debugStr, result.nodeSet);
2✔
651
            buf.push_back(std::move(result));
2✔
652
            break;
2✔
653
        }
654
        case SimulationConfig::ModificationBase::ModificationType::ConfigureAllSections: {
2✔
655
            SimulationConfig::ModificationConfigureAllSections result;
4✔
656
            result.type = type;
2✔
657
            parseMandatory(valueIt, "name", debugStr, result.name);
2✔
658
            parseMandatory(valueIt, "node_set", debugStr, result.nodeSet);
2✔
659
            parseMandatory(valueIt, "section_configure", debugStr, result.sectionConfigure);
2✔
660
            buf.push_back(std::move(result));
2✔
661
            break;
2✔
662
        }
663
        default:
×
664
            throw SonataError("Unknown modificationn type in " + debugStr);
×
665
        }
666
    }
667
}
668

669
}  // namespace
670

671
class CircuitConfig::Parser
672
{
673
  public:
674
    Parser(const std::string& contents, const std::string& basePath)
17✔
675
        : _basePath(fs::absolute(fs::path(basePath))) {
23✔
676
        // Parse and expand JSON string
677
        const auto rawJson = nlohmann::json::parse(contents);
34✔
678
        const auto vars = replaceVariables(readVariables(rawJson));
19✔
679
        _json = expandVariables(rawJson, vars);
14✔
680
    }
14✔
681

682
    template <typename T>
683
    T getJSONValue(const nlohmann::json& json,
253✔
684
                   const std::string& key,
685
                   const T& defaultValue = T()) const {
686
        auto it = json.find(key);
253✔
687
        if (it != json.end() && !it->is_null()) {
253✔
688
            return it.value().get<T>();
63✔
689
        }
690

691
        return defaultValue;
190✔
692
    }
693

694
    nonstd::optional<std::string> getOptionalJSONPath(const nlohmann::json& json,
108✔
695
                                                      const std::string& key) const {
696
        auto value = getJSONValue<std::string>(json, key);
216✔
697
        if (!value.empty()) {
108✔
698
            return toAbsolute(_basePath, value);
16✔
699
        }
700

701
        return nonstd::nullopt;
92✔
702
    }
703

704
    std::string getJSONPath(const nlohmann::json& json,
121✔
705
                            const std::string& key,
706
                            const std::string& defaultValue = std::string()) const {
707
        auto value = getJSONValue<std::string>(json, key);
242✔
708
        if (!value.empty()) {
121✔
709
            return toAbsolute(_basePath, value);
41✔
710
        }
711

712
        return defaultValue;
80✔
713
    }
714

715
    ConfigStatus getCircuitConfigStatus() const {
14✔
716
        if (_json.find("metadata") == _json.end()) {
14✔
717
            return ConfigStatus::complete;
14✔
718
        }
719
        const auto& metadata = _json.at("metadata");
×
720
        const auto res = getJSONValue<ConfigStatus>(metadata, "status", {ConfigStatus::complete});
×
721

722
        if (res == ConfigStatus::invalid) {
×
723
            throw SonataError("Invalid value for `metadata::ConfigStatus` in config");
×
724
        }
725

726
        return res;
×
727
    }
728

729
    nlohmann::json getSubNetworkJson(const std::string& prefix,
25✔
730
                                     CircuitConfig::ConfigStatus ConfigStatus) const {
731
        // Fail if no network entry is defined
732
        if (_json.find("networks") == _json.end()) {
25✔
733
            if (ConfigStatus == CircuitConfig::ConfigStatus::complete) {
1✔
734
                throw SonataError("Error parsing config: `networks` not specified");
1✔
735
            } else {
736
                return {};
×
737
            }
738
        }
739

740
        const auto& networks = _json.at("networks");
24✔
741

742
        const std::string component = prefix + "s";
48✔
743
        if (networks.find(component) == networks.end()) {
24✔
744
            if (ConfigStatus == CircuitConfig::ConfigStatus::complete) {
3✔
745
                throw SonataError(
746
                    fmt::format("Error parsing networks config: '{}' not specified", component));
6✔
747
            } else {
748
                return {};
×
749
            }
750
        }
751

752
        return networks.at(component);
21✔
753
    }
754

755
    std::string getNodeSetsPath() const {
14✔
756
        // Retrieve node sets file, if any
757
        if (_json.find("node_sets_file") != _json.end()) {
14✔
758
            return toAbsolute(_basePath, _json["node_sets_file"]);
4✔
759
        }
760

761
        return {};
10✔
762
    }
763

764
    CircuitConfig::Components parseDefaultComponents() const {
9✔
765
        CircuitConfig::Components result;
9✔
766

767
        if (_json.find("components") == _json.end()) {
9✔
768
            return result;
×
769
        }
770

771
        const auto& components = _json.at("components");
9✔
772

773
        result.morphologiesDir = getJSONPath(components, "morphologies_dir");
9✔
774

775
        const auto alternateMorphoDir = components.find("alternate_morphologies");
9✔
776
        if (alternateMorphoDir != components.end()) {
9✔
777
            for (auto it = alternateMorphoDir->begin(); it != alternateMorphoDir->end(); ++it) {
6✔
778
                result.alternateMorphologiesDir[it.key()] = toAbsolute(_basePath, it.value());
3✔
779
            }
780
        }
781

782
        result.biophysicalNeuronModelsDir = getJSONPath(components,
18✔
783
                                                        "biophysical_neuron_models_dir");
9✔
784

785
        result.vasculatureFile = getOptionalJSONPath(components, "vasculature_file");
9✔
786
        result.vasculatureMesh = getOptionalJSONPath(components, "vasculature_mesh");
9✔
787
        result.endfeetMeshesFile = getOptionalJSONPath(components, "endfeet_meshes_file");
9✔
788
        result.microdomainsFile = getOptionalJSONPath(components, "microdomains_file");
9✔
789
        result.spineMorphologiesDir = getOptionalJSONPath(components, "spine_morphologies_dir");
9✔
790

791
        return result;
9✔
792
    }
793

794
    template <typename PopulationPropertiesT>
795
    void updateDefaultProperties(PopulationPropertiesT& component,
24✔
796
                                 const std::string& defaultType,
797
                                 const CircuitConfig::Components& defaultComponents) {
798
        if (component.type.empty()) {
24✔
799
            component.type = defaultType;
20✔
800
        }
801

802
        if (component.alternateMorphologyFormats.empty()) {
24✔
803
            component.alternateMorphologyFormats = defaultComponents.alternateMorphologiesDir;
22✔
804
        }
805

806
        if (component.biophysicalNeuronModelsDir.empty()) {
24✔
807
            component.biophysicalNeuronModelsDir = defaultComponents.biophysicalNeuronModelsDir;
23✔
808
        }
809

810
        if (component.morphologiesDir.empty()) {
24✔
811
            component.morphologiesDir = defaultComponents.morphologiesDir;
22✔
812
        }
813
    }
24✔
814

815
    void updateDefaultNodeProperties(std::unordered_map<std::string, NodePopulationProperties>& map,
9✔
816
                                     const std::string& defaultType,
817
                                     const CircuitConfig::Components& defaultComponents) {
818
        for (auto& entry : map) {
24✔
819
            auto& component = entry.second;
15✔
820
            updateDefaultProperties(component, defaultType, defaultComponents);
15✔
821

822
            if (!component.vasculatureFile) {
15✔
823
                component.vasculatureFile = defaultComponents.vasculatureFile;
15✔
824
            }
825

826
            if (!component.vasculatureMesh) {
15✔
827
                component.vasculatureMesh = defaultComponents.vasculatureMesh;
15✔
828
            }
829

830
            if (!component.microdomainsFile) {
15✔
831
                component.microdomainsFile = defaultComponents.microdomainsFile;
15✔
832
            }
833
        }
834
    }
9✔
835

836
    void updateDefaultEdgeProperties(std::unordered_map<std::string, EdgePopulationProperties>& map,
9✔
837
                                     const std::string& defaultType,
838
                                     const CircuitConfig::Components& defaultComponents) {
839
        for (auto& entry : map) {
18✔
840
            auto& component = entry.second;
9✔
841
            updateDefaultProperties(component, defaultType, defaultComponents);
9✔
842

843
            if (!component.endfeetMeshesFile) {
9✔
844
                component.endfeetMeshesFile = defaultComponents.endfeetMeshesFile;
9✔
845
            }
846
            if (!component.spineMorphologiesDir) {
9✔
847
                component.spineMorphologiesDir = defaultComponents.spineMorphologiesDir;
5✔
848
            }
849
        }
850
    }
9✔
851

852
    template <typename JSON>
853
    std::tuple<std::string, std::string> parseSubNetworks(const std::string& prefix,
16✔
854
                                                          const JSON& value) const {
855
        const std::string elementsFile = prefix + "s_file";
32✔
856
        auto h5File = getJSONPath(value, elementsFile);
32✔
857

858
        if (h5File.empty()) {
16✔
859
            throw SonataError(
860
                fmt::format("'{}' network do not define '{}' entry", prefix, elementsFile));
2✔
861
        }
862

863
        const std::string typesFile = prefix + "_types_file";
30✔
864
        auto csvFile = getJSONPath(value, typesFile);
30✔
865

866
        return {h5File, csvFile};
30✔
867
    }
868

869
    template <typename PopulationProperties, typename Func>
870
    std::unordered_map<std::string, PopulationProperties> parsePopulationProperties(
25✔
871
        const std::string& prefix, CircuitConfig::ConfigStatus status, const Func& parser) const {
872
        const auto& network = getSubNetworkJson(prefix, status);
46✔
873

874
        std::unordered_map<std::string, PopulationProperties> output;
21✔
875

876
        for (const auto& subnetwork : network) {
51✔
877
            std::string elementsPath;
17✔
878
            std::string typesPath;
17✔
879
            std::tie(elementsPath, typesPath) = parseSubNetworks(prefix, subnetwork);
16✔
880

881
            const auto populationsIt = subnetwork.find("populations");
15✔
882
            if (populationsIt == subnetwork.end()) {
15✔
883
                continue;
2✔
884
            }
885

886
            for (auto it = populationsIt->begin(); it != populationsIt->end(); ++it) {
37✔
887
                const auto& popData = it.value();
24✔
888

889
                if (output.find(it.key()) != output.end()) {
24✔
890
                    throw SonataError(fmt::format("Population {} is declared twice", it.key()));
×
891
                }
892

893
                PopulationProperties& popProperties = output[it.key()];
24✔
894

895
                popProperties.elementsPath = elementsPath;
24✔
896
                popProperties.typesPath = typesPath;
24✔
897

898
                popProperties.type = getJSONValue<std::string>(popData, "type");
24✔
899
                popProperties.morphologiesDir = getJSONPath(popData, "morphologies_dir");
24✔
900
                popProperties.biophysicalNeuronModelsDir =
24✔
901
                    getJSONPath(popData, "biophysical_neuron_models_dir");
902

903
                // Overwrite those specified, if any
904
                const auto altMorphoDir = popData.find("alternate_morphologies");
24✔
905
                if (altMorphoDir != popData.end()) {
24✔
906
                    for (auto it = altMorphoDir->begin(); it != altMorphoDir->end(); ++it) {
4✔
907
                        popProperties.alternateMorphologyFormats[it.key()] = toAbsolute(_basePath,
2✔
908
                                                                                        it.value());
2✔
909
                    }
910
                }
911

912
                parser(popProperties, popData);
24✔
913
            }
914
        }
915

916
        return output;
40✔
917
    }
918

919
    std::unordered_map<std::string, NodePopulationProperties> parseNodePopulations(
14✔
920
        CircuitConfig::ConfigStatus status) const {
921
        return parsePopulationProperties<NodePopulationProperties>(
922
            "node",
923
            status,
924
            [&](NodePopulationProperties& popProperties, const nlohmann::json& popData) {
15✔
925
            popProperties.spatialSegmentIndexDir = getJSONPath(popData, "spatial_segment_index_dir");
15✔
926
            popProperties.vasculatureFile = getOptionalJSONPath(popData, "vasculature_file");
15✔
927
            popProperties.vasculatureMesh = getOptionalJSONPath(popData, "vasculature_mesh");
15✔
928
            popProperties.microdomainsFile = getOptionalJSONPath(popData, "microdomains_file");
15✔
929
            });
35✔
930
    }
931

932
    std::unordered_map<std::string, EdgePopulationProperties> parseEdgePopulations(
11✔
933
        CircuitConfig::ConfigStatus status) const {
934
        return parsePopulationProperties<EdgePopulationProperties>(
935
            "edge",
936
            status,
937
            [&](EdgePopulationProperties& popProperties, const nlohmann::json& popData) {
9✔
938
            popProperties.spatialSynapseIndexDir = getJSONPath(popData, "spatial_synapse_index_dir");
9✔
939
            popProperties.endfeetMeshesFile = getOptionalJSONPath(popData, "endfeet_meshes_file");
9✔
940
            popProperties.spineMorphologiesDir = getOptionalJSONPath(popData,
18✔
941
                                                                     "spine_morphologies_dir");
9✔
942
            });
24✔
943
    }
944

945
    std::string getExpandedJSON() const {
14✔
946
        return _json.dump();
14✔
947
    }
948

949
  private:
950
    const fs::path _basePath;
951
    nlohmann::json _json;
952
};
953

954
CircuitConfig::CircuitConfig(const std::string& contents, const std::string& basePath) {
57✔
955
    Parser parser(contents, basePath);
31✔
956

957
    _expandedJSON = parser.getExpandedJSON();
14✔
958
    _status = parser.getCircuitConfigStatus();
14✔
959

960
    _nodeSetsFile = parser.getNodeSetsPath();
14✔
961

962
    _nodePopulationProperties = parser.parseNodePopulations(_status);
14✔
963
    _edgePopulationProperties = parser.parseEdgePopulations(_status);
11✔
964

965
    Components defaultComponents = parser.parseDefaultComponents();
18✔
966

967
    parser.updateDefaultNodeProperties(_nodePopulationProperties, "biophysical", defaultComponents);
9✔
968
    parser.updateDefaultEdgeProperties(_edgePopulationProperties, "chemical", defaultComponents);
9✔
969

970
    if (getCircuitConfigStatus() == ConfigStatus::complete) {
9✔
971
        for (const auto& it : _nodePopulationProperties) {
22✔
972
            const auto& population = it.first;
15✔
973
            const auto& properties = it.second;
15✔
974
            if (properties.type == "biophysical") {
15✔
975
                raiseOnBiophysicalPopulationErrors(population, properties);
13✔
976
            } else if (properties.type == "vasculature") {
2✔
977
                raiseOnVasculaturePopulationErrors(population, properties);
×
978
            } else if (properties.type == "astrocyte") {
2✔
979
                raiseOnAstrocytePopulationErrors(population, properties);
×
980
            }
981
        }
982

983
        for (const auto& it : _edgePopulationProperties) {
16✔
984
            const auto& population = it.first;
9✔
985
            const auto& properties = it.second;
9✔
986
            if (properties.type == "endfoot") {
9✔
987
                raiseOnEndfootPopulationErrors(population, properties);
×
988
            }
989
        }
990
    }
991
}
7✔
992

993
CircuitConfig CircuitConfig::fromFile(const std::string& path) {
7✔
994
    return {readFile(path), fs::path(path).parent_path()};
7✔
995
}
996

997
CircuitConfig::ConfigStatus CircuitConfig::getCircuitConfigStatus() const {
9✔
998
    return _status;
9✔
999
}
1000

1001
const std::string& CircuitConfig::getNodeSetsPath() const {
5✔
1002
    return _nodeSetsFile;
5✔
1003
}
1004

1005
std::set<std::string> CircuitConfig::listNodePopulations() const {
1✔
1006
    return getMapKeys(_nodePopulationProperties);
1✔
1007
}
1008

1009
NodePopulation CircuitConfig::getNodePopulation(const std::string& name) const {
2✔
1010
    return getNodePopulation(name, Hdf5Reader());
3✔
1011
}
1012

1013
NodePopulation CircuitConfig::getNodePopulation(const std::string& name,
2✔
1014
                                                const Hdf5Reader& hdf5_reader) const {
1015
    return getPopulation<NodePopulation>(name, _nodePopulationProperties, hdf5_reader);
2✔
1016
}
1017

1018
std::set<std::string> CircuitConfig::listEdgePopulations() const {
1✔
1019
    return getMapKeys(_edgePopulationProperties);
1✔
1020
}
1021

1022
EdgePopulation CircuitConfig::getEdgePopulation(const std::string& name) const {
2✔
1023
    return getPopulation<EdgePopulation>(name, _edgePopulationProperties, Hdf5Reader());
3✔
1024
}
1025

1026
EdgePopulation CircuitConfig::getEdgePopulation(const std::string& name,
×
1027
                                                const Hdf5Reader& hdf5_reader) const {
1028
    return getPopulation<EdgePopulation>(name, _edgePopulationProperties, hdf5_reader);
×
1029
}
1030

1031
NodePopulationProperties CircuitConfig::getNodePopulationProperties(const std::string& name) const {
6✔
1032
    return getPopulationProperties(name, _nodePopulationProperties);
6✔
1033
}
1034

1035
EdgePopulationProperties CircuitConfig::getEdgePopulationProperties(const std::string& name) const {
5✔
1036
    return getPopulationProperties(name, _edgePopulationProperties);
5✔
1037
}
1038

1039
const std::string& CircuitConfig::getExpandedJSON() const {
2✔
1040
    return _expandedJSON;
2✔
1041
}
1042

1043
class SimulationConfig::Parser
1044
{
1045
  public:
1046
    Parser(const std::string& content, const std::string& basePath)
37✔
1047
        : _basePath(fs::absolute(fs::path(basePath)).lexically_normal()) {
37✔
1048
        // Parse manifest section and expand JSON string
1049
        const auto rawJson = nlohmann::json::parse(content);
74✔
1050
        const auto vars = replaceVariables(readVariables(rawJson));
37✔
1051
        _json = expandVariables(rawJson, vars);
37✔
1052
    }
37✔
1053

1054
    SimulationConfig::Run parseRun() const {
37✔
1055
        const auto runIt = _json.find("run");
37✔
1056
        if (runIt == _json.end()) {
37✔
1057
            throw SonataError("Could not find 'run' section");
1✔
1058
        }
1059

1060
        SimulationConfig::Run result{};
36✔
1061
        parseMandatory(*runIt, "tstop", "run", result.tstop);
38✔
1062
        parseMandatory(*runIt, "dt", "run", result.dt);
37✔
1063
        parseMandatory(*runIt, "random_seed", "run", result.randomSeed);
50✔
1064
        parseOptional(*runIt,
52✔
1065
                      "spike_threshold",
1066
                      result.spikeThreshold,
26✔
1067
                      {Run::DEFAULT_spikeThreshold});
1068
        parseOptional(*runIt,
53✔
1069
                      "integration_method",
1070
                      result.integrationMethod,
26✔
1071
                      {Run::DEFAULT_IntegrationMethod});
1072
        parseOptional(*runIt, "stimulus_seed", result.stimulusSeed, {Run::DEFAULT_stimulusSeed});
25✔
1073
        parseOptional(*runIt,
50✔
1074
                      "ionchannel_seed",
1075
                      result.ionchannelSeed,
25✔
1076
                      {Run::DEFAULT_ionchannelSeed});
1077
        parseOptional(*runIt, "minis_seed", result.minisSeed, {Run::DEFAULT_minisSeed});
25✔
1078
        parseOptional(*runIt, "synapse_seed", result.synapseSeed, {Run::DEFAULT_synapseSeed});
25✔
1079
        parseOptional(*runIt, "electrodes_file", result.electrodesFile, {""});
25✔
1080

1081
        if (!result.electrodesFile.empty()) {
25✔
1082
            result.electrodesFile = toAbsolute(_basePath, result.electrodesFile);
2✔
1083
        }
1084

1085
        return result;
50✔
1086
    }
1087

1088
    SimulationConfig::Output parseOutput() const {
25✔
1089
        SimulationConfig::Output result{};
25✔
1090

1091
        const auto outputIt = _json.find("output");
25✔
1092
        if (outputIt == _json.end()) {
25✔
1093
            return result;
23✔
1094
        }
1095
        parseOptional(*outputIt, "output_dir", result.outputDir, {Output::DEFAULT_outputDir});
2✔
1096
        parseOptional(*outputIt, "log_file", result.logFile, {Output::DEFAULT_logFile});
2✔
1097
        parseOptional(*outputIt, "spikes_file", result.spikesFile, {Output::DEFAULT_spikesFile});
2✔
1098
        parseOptional(*outputIt,
4✔
1099
                      "spikes_sort_order",
1100
                      result.sortOrder,
2✔
1101
                      {Output::DEFAULT_sortOrder});
1102

1103
        result.outputDir = toAbsolute(_basePath, result.outputDir);
2✔
1104

1105
        return result;
2✔
1106
    }
1107

1108
    SimulationConfig::Conditions parseConditions() const {
25✔
1109
        SimulationConfig::Conditions result;
25✔
1110

1111
        const auto conditionsIt = _json.find("conditions");
25✔
1112
        if (conditionsIt == _json.end()) {
25✔
1113
            return result;
22✔
1114
        }
1115
        parseOptional(*conditionsIt, "celsius", result.celsius, {Conditions::DEFAULT_celsius});
3✔
1116
        parseOptional(*conditionsIt, "v_init", result.vInit, {Conditions::DEFAULT_vInit});
3✔
1117
        parseOptional(*conditionsIt,
7✔
1118
                      "spike_location",
1119
                      result.spikeLocation,
3✔
1120
                      {Conditions::DEFAULT_spikeLocation});
1121
        parseOptional(*conditionsIt, "extracellular_calcium", result.extracellularCalcium);
2✔
1122
        parseOptional(*conditionsIt,
4✔
1123
                      "randomize_gaba_rise_time",
1124
                      result.randomizeGabaRiseTime,
2✔
1125
                      {Conditions::DEFAULT_randomizeGabaRiseTime});
1126
        parseConditionsMechanisms(*conditionsIt, result.mechanisms);
2✔
1127
        parseConditionsModifications(*conditionsIt, result.modifications);
2✔
1128
        return result;
2✔
1129
    }
1130

1131
    ReportMap parseReports(const SimulationConfig::Output& output) const {
24✔
1132
        ReportMap result;
24✔
1133

1134
        const auto reportsIt = _json.find("reports");
24✔
1135
        if (reportsIt == _json.end()) {
24✔
1136
            return result;
12✔
1137
        }
1138

1139
        for (auto it = reportsIt->begin(); it != reportsIt->end(); ++it) {
22✔
1140
            auto& report = result[it.key()];
20✔
1141
            const auto& valueIt = it.value();
20✔
1142
            const std::string debugStr = "report " + it.key();
40✔
1143

1144
            parseOptional(valueIt, "cells", report.cells, parseNodeSet());
20✔
1145
            parseOptional(valueIt, "sections", report.sections, {Report::Sections::soma});
20✔
1146
            parseMandatory(valueIt, "type", debugStr, report.type);
20✔
1147
            parseOptional(valueIt, "scaling", report.scaling, {Report::Scaling::area});
18✔
1148
            parseOptional(valueIt,
18✔
1149
                          "compartments",
1150
                          report.compartments,
18✔
1151
                          {report.sections == Report::Sections::soma ? Report::Compartments::center
18✔
1152
                                                                     : Report::Compartments::all});
1153
            parseMandatory(valueIt, "variable_name", debugStr, report.variableName);
18✔
1154
            parseOptional(valueIt, "unit", report.unit, {"mV"});
17✔
1155
            parseMandatory(valueIt, "dt", debugStr, report.dt);
17✔
1156
            parseMandatory(valueIt, "start_time", debugStr, report.startTime);
16✔
1157
            parseMandatory(valueIt, "end_time", debugStr, report.endTime);
15✔
1158
            parseOptional(valueIt, "file_name", report.fileName, {it.key() + ".h5"});
14✔
1159
            parseOptional(valueIt, "enabled", report.enabled, {true});
14✔
1160

1161
            // variable names can look like:
1162
            // `v`, or `i_clamp`, or `Foo.bar` but not `..asdf`, or `asdf..` or `asdf.asdf.asdf`
1163
            const char* const varName = R"(\w+(?:\.?\w+)?)";
14✔
1164
            // variable names are separated by `,` with any amount of whitespace separating them
1165
            const std::regex expr(fmt::format(R"({}(?:\s*,\s*{})*)", varName, varName));
28✔
1166
            if (!std::regex_match(report.variableName, expr)) {
14✔
1167
                throw SonataError(fmt::format("Invalid comma separated variable names '{}'",
8✔
1168
                                              report.variableName));
12✔
1169
            }
1170

1171
            const auto extension = fs::path(report.fileName).extension().string();
20✔
1172
            if (extension.empty() || extension != ".h5") {
10✔
1173
                report.fileName += ".h5";
4✔
1174
            }
1175
            report.fileName = toAbsolute(output.outputDir, report.fileName);
10✔
1176
        }
1177

1178
        return result;
2✔
1179
    }
1180

1181
    std::string parseNetwork() const {
18✔
1182
        auto val = _json.value("network", "circuit_config.json");
36✔
1183
        return toAbsolute(_basePath, val);
36✔
1184
    }
1185

1186
    SimulationConfig::SimulatorType parseTargetSimulator() const {
5✔
1187
        SimulationConfig::SimulatorType val = SimulationConfig::SimulatorType::NEURON;
5✔
1188
        parseOptional(_json, "target_simulator", val, {SimulationConfig::SimulatorType::NEURON});
6✔
1189
        return val;
4✔
1190
    }
1191

1192
    std::string parseNodeSetsFile() const noexcept {
4✔
1193
        std::string val;
8✔
1194
        if (_json.contains("node_sets_file")) {
4✔
1195
            val = _json["node_sets_file"];
×
1196
            return toAbsolute(_basePath, val);
×
1197
        } else {
1198
            try {
1199
                const auto circuitFile = parseNetwork();
8✔
1200
                const auto conf = CircuitConfig::fromFile(circuitFile);
6✔
1201
                return conf.getNodeSetsPath();
2✔
1202
            } catch (...) {
2✔
1203
                // Don't throw CircuitConfig exceptions in SimulationConfig and return empty string
1204
                return val;
2✔
1205
            }
1206
        }
1207
    }
1208

1209
    nonstd::optional<std::string> parseNodeSet() const {
24✔
1210
        if (_json.contains("node_set")) {
24✔
1211
            return {_json["node_set"]};
12✔
1212
        } else {
1213
            return nonstd::nullopt;
12✔
1214
        }
1215
    }
1216

1217
    InputMap parseInputs() const {
14✔
1218
        InputMap result;
14✔
1219

1220
        const auto inputsIt = _json.find("inputs");
14✔
1221
        if (inputsIt == _json.end()) {
14✔
1222
            return result;
4✔
1223
        }
1224

1225
        for (auto it = inputsIt->begin(); it != inputsIt->end(); ++it) {
44✔
1226
            const auto& valueIt = it.value();
42✔
1227
            const auto debugStr = fmt::format("input {}", it.key());
126✔
1228

1229
            InputBase::Module module = InputBase::Module::invalid;
42✔
1230
            parseMandatory(valueIt, "module", debugStr, module);
42✔
1231

1232
            const auto input = parseInputModule(valueIt, module, _basePath, debugStr);
78✔
1233
            result[it.key()] = input;
35✔
1234

1235
            auto mismatchingModuleInputType = [&it]() {
2✔
1236
                const auto module_name = it->find("module")->get<std::string>();
2✔
1237
                const auto input_type = it->find("input_type")->get<std::string>();
2✔
1238
                throw SonataError(
1239
                    fmt::format("An `input` has module `{}` and input_type `{}` which mismatch",
2✔
1240
                                module_name,
1241
                                input_type));
3✔
1242
            };
35✔
1243

1244
            auto inputType = nonstd::visit([](const auto& v) { return v.inputType; }, input);
70✔
1245
            switch (inputType) {
35✔
1246
            case InputBase::InputType::current_clamp: {
24✔
1247
                if (!(nonstd::holds_alternative<SimulationConfig::InputLinear>(input) ||
46✔
1248
                      nonstd::holds_alternative<SimulationConfig::InputRelativeLinear>(input) ||
22✔
1249
                      nonstd::holds_alternative<SimulationConfig::InputPulse>(input) ||
20✔
1250
                      nonstd::holds_alternative<SimulationConfig::InputSinusoidal>(input) ||
18✔
1251
                      nonstd::holds_alternative<SimulationConfig::InputSubthreshold>(input) ||
14✔
1252
                      nonstd::holds_alternative<SimulationConfig::InputNoise>(input) ||
12✔
1253
                      nonstd::holds_alternative<SimulationConfig::InputShotNoise>(input) ||
8✔
1254
                      nonstd::holds_alternative<SimulationConfig::InputRelativeShotNoise>(input) ||
6✔
1255
                      nonstd::holds_alternative<SimulationConfig::InputAbsoluteShotNoise>(input) ||
4✔
1256
                      nonstd::holds_alternative<SimulationConfig::InputHyperpolarizing>(input) ||
4✔
1257
                      nonstd::holds_alternative<SimulationConfig::InputOrnsteinUhlenbeck>(input) ||
2✔
1258
                      nonstd::holds_alternative<SimulationConfig::InputRelativeOrnsteinUhlenbeck>(
2✔
1259
                          input))) {
1260
                    mismatchingModuleInputType();
×
1261
                }
1262
            } break;
24✔
1263
            case InputBase::InputType::spikes:
2✔
1264
                if (!nonstd::holds_alternative<SimulationConfig::InputSynapseReplay>(input)) {
2✔
1265
                    mismatchingModuleInputType();
×
1266
                }
1267
                break;
2✔
1268
            case InputBase::InputType::voltage_clamp:
3✔
1269
                if (!nonstd::holds_alternative<SimulationConfig::InputSeclamp>(input)) {
3✔
1270
                    mismatchingModuleInputType();
1✔
1271
                }
1272
                break;
2✔
1273
            case InputBase::InputType::extracellular_stimulation:
2✔
1274
                break;
2✔
1275
            case InputBase::InputType::conductance:
4✔
1276
                if (!(nonstd::holds_alternative<SimulationConfig::InputShotNoise>(input) ||
8✔
1277
                      nonstd::holds_alternative<SimulationConfig::InputRelativeShotNoise>(input) ||
4✔
1278
                      nonstd::holds_alternative<SimulationConfig::InputAbsoluteShotNoise>(input) ||
4✔
1279
                      nonstd::holds_alternative<SimulationConfig::InputOrnsteinUhlenbeck>(input) ||
2✔
1280
                      nonstd::holds_alternative<SimulationConfig::InputRelativeOrnsteinUhlenbeck>(
×
1281
                          input))) {
1282
                    mismatchingModuleInputType();
×
1283
                }
1284
                break;
4✔
1285
            default:
×
1286
                throw SonataError(fmt::format("Unknown input_type in {}", debugStr));
×
1287
            }
1288
        }
1289
        return result;
2✔
1290
    }
1291

1292
    std::vector<ConnectionOverride> parseConnectionOverrides() const {
6✔
1293
        std::vector<ConnectionOverride> result;
6✔
1294

1295
        const auto connIt = _json.find("connection_overrides");
6✔
1296
        // nlohmann::json::flatten().unflatten() converts empty containers to `null`:
1297
        // https://json.nlohmann.me/api/basic_json/unflatten/#notes
1298
        // so we can't tell the difference between {} and []; however, since these are
1299
        // empty, we will assume the intent was to have no connection_overrides and forgo
1300
        // better error reporting
1301
        if (connIt == _json.end() || connIt->is_null()) {
6✔
1302
            return result;
3✔
1303
        }
1304

1305
        if (!connIt->is_array()) {
3✔
1306
            throw SonataError("`connection_overrides` must be an array");
1✔
1307
        }
1308

1309
        result.reserve(connIt->size());
2✔
1310

1311
        for (auto it = connIt->begin(); it != connIt->end(); ++it) {
6✔
1312
            const auto& valueIt = it.value();
4✔
1313
            ConnectionOverride connect;
8✔
1314
            parseMandatory(valueIt, "name", "connection_override", connect.name);
4✔
1315
            const auto debugStr = fmt::format("connection_override {}", connect.name);
8✔
1316
            parseMandatory(valueIt, "source", debugStr, connect.source);
4✔
1317
            parseMandatory(valueIt, "target", debugStr, connect.target);
4✔
1318
            parseOptional(valueIt, "weight", connect.weight);
4✔
1319
            parseOptional(valueIt, "spont_minis", connect.spontMinis);
4✔
1320
            parseOptional(valueIt, "synapse_configure", connect.synapseConfigure);
4✔
1321
            parseOptional(valueIt, "modoverride", connect.modoverride);
4✔
1322
            parseOptional(valueIt, "synapse_delay_override", connect.synapseDelayOverride);
4✔
1323
            parseOptional(valueIt, "delay", connect.delay);
4✔
1324
            parseOptional(valueIt, "neuromodulation_dtc", connect.neuromodulationDtc);
4✔
1325
            parseOptional(valueIt, "neuromodulation_strength", connect.neuromodulationStrength);
4✔
1326

1327
            result.push_back(std::move(connect));
4✔
1328
        }
1329
        return result;
2✔
1330
    }
1331

1332
    std::unordered_map<std::string, variantValueType> parseMetaData() const {
4✔
1333
        std::unordered_map<std::string, variantValueType> result;
4✔
1334
        const auto metaIt = _json.find("metadata");
4✔
1335
        if (metaIt == _json.end()) {
4✔
1336
            return result;
2✔
1337
        }
1338
        for (auto& it : metaIt->items()) {
10✔
1339
            variantValueType res_val;
8✔
1340
            parseVariantType(it.value(), res_val);
8✔
1341
            result.insert({it.key(), res_val});
8✔
1342
        }
1343
        return result;
2✔
1344
    }
1345

1346
    std::unordered_map<std::string, variantValueType> parseBetaFeatures() const {
4✔
1347
        std::unordered_map<std::string, variantValueType> result;
4✔
1348
        const auto fIt = _json.find("beta_features");
4✔
1349
        if (fIt == _json.end()) {
4✔
1350
            return result;
2✔
1351
        }
1352
        for (auto& it : fIt->items()) {
10✔
1353
            variantValueType res_val;
8✔
1354
            parseVariantType(it.value(), res_val);
8✔
1355
            result.insert({it.key(), res_val});
8✔
1356
        }
1357
        return result;
2✔
1358
    }
1359

1360
    std::string getExpandedJSON() const {
37✔
1361
        return _json.dump();
37✔
1362
    }
1363

1364
  private:
1365
    const fs::path _basePath;
1366
    nlohmann::json _json;
1367
};
1368

1369
SimulationConfig::SimulationConfig(const std::string& content, const std::string& basePath)
37✔
1370
    : _basePath(fs::absolute(basePath).lexically_normal().string()) {
466✔
1371
    const Parser parser(content, basePath);
70✔
1372
    _expandedJSON = parser.getExpandedJSON();
37✔
1373
    _run = parser.parseRun();
37✔
1374
    _output = parser.parseOutput();
25✔
1375
    _conditions = parser.parseConditions();
25✔
1376
    _reports = parser.parseReports(_output);
24✔
1377
    _network = parser.parseNetwork();
14✔
1378
    _inputs = parser.parseInputs();
14✔
1379
    _connection_overrides = parser.parseConnectionOverrides();
6✔
1380
    _targetSimulator = parser.parseTargetSimulator();
5✔
1381
    _nodeSetsFile = parser.parseNodeSetsFile();
4✔
1382
    _nodeSet = parser.parseNodeSet();
4✔
1383
    _metaData = parser.parseMetaData();
4✔
1384
    _betaFeatures = parser.parseBetaFeatures();
4✔
1385
}
4✔
1386

1387
SimulationConfig SimulationConfig::fromFile(const std::string& path) {
2✔
1388
    return SimulationConfig(readFile(path), fs::path(path).parent_path());
4✔
1389
}
1390

1391
const std::string& SimulationConfig::getBasePath() const noexcept {
1✔
1392
    return _basePath;
1✔
1393
}
1394

1395
const SimulationConfig::Run& SimulationConfig::getRun() const noexcept {
17✔
1396
    return _run;
17✔
1397
}
1398

1399
const SimulationConfig::Output& SimulationConfig::getOutput() const noexcept {
7✔
1400
    return _output;
7✔
1401
}
1402

1403
const SimulationConfig::Conditions& SimulationConfig::getConditions() const noexcept {
11✔
1404
    return _conditions;
11✔
1405
}
1406

1407
const std::vector<SimulationConfig::Modification>& SimulationConfig::Conditions::getModifications()
1✔
1408
    const noexcept {
1409
    return modifications;
1✔
1410
}
1411

1412
const std::string& SimulationConfig::getNetwork() const noexcept {
3✔
1413
    return _network;
3✔
1414
}
1415

1416
std::set<std::string> SimulationConfig::listReportNames() const {
1✔
1417
    return getMapKeys(_reports);
1✔
1418
}
1419

1420
const SimulationConfig::Report& SimulationConfig::getReport(const std::string& name) const {
17✔
1421
    const auto it = _reports.find(name);
17✔
1422
    if (it == _reports.end()) {
17✔
1423
        throw SonataError(
1424
            fmt::format("The report '{}' is not present in the simulation config file", name));
2✔
1425
    }
1426

1427
    return it->second;
16✔
1428
}
1429

1430
std::set<std::string> SimulationConfig::listInputNames() const {
1✔
1431
    return getMapKeys(_inputs);
1✔
1432
}
1433

1434
const SimulationConfig::Input& SimulationConfig::getInput(const std::string& name) const {
17✔
1435
    const auto it = _inputs.find(name);
17✔
1436
    if (it == _inputs.end()) {
17✔
1437
        throw SonataError(
1438
            fmt::format("The input '{}' is not present in the simulation config file", name));
2✔
1439
    }
1440
    return it->second;
16✔
1441
}
1442

1443
const std::vector<SimulationConfig::ConnectionOverride>& SimulationConfig::getConnectionOverrides()
1✔
1444
    const noexcept {
1445
    return _connection_overrides;
1✔
1446
}
1447

1448
const SimulationConfig::SimulatorType& SimulationConfig::getTargetSimulator() const {
2✔
1449
    return _targetSimulator;
2✔
1450
}
1451

1452
const std::string& SimulationConfig::getNodeSetsFile() const noexcept {
2✔
1453
    return _nodeSetsFile;
2✔
1454
}
1455

1456
const nonstd::optional<std::string>& SimulationConfig::getNodeSet() const noexcept {
2✔
1457
    return _nodeSet;
2✔
1458
}
1459

1460
const std::unordered_map<std::string, variantValueType>& SimulationConfig::getMetaData() const
1✔
1461
    noexcept {
1462
    return _metaData;
1✔
1463
}
1464

1465
const std::unordered_map<std::string, variantValueType>& SimulationConfig::getBetaFeatures() const
1✔
1466
    noexcept {
1467
    return _betaFeatures;
1✔
1468
}
1469

1470
const std::string& SimulationConfig::getExpandedJSON() const {
1✔
1471
    return _expandedJSON;
1✔
1472
}
1473

1474
}  // namespace sonata
1475
}  // 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