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

BlueBrain / libsonata / 3967731590

pending completion
3967731590

push

github

Mike Gevaert
allow missing nodes/edges in partial config

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

1687 of 1729 relevant lines covered (97.57%)

73.81 hits per line

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

95.94
/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) {
42✔
44
        if (j.is_null()) {
42✔
45
            opt = nonstd::nullopt;
6✔
46
        } else {
47
            opt = j.get<T>();
36✔
48
        }
49
    }
42✔
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::Run::SpikeLocation,
266✔
62
                             {{SimulationConfig::Run::SpikeLocation::invalid, nullptr},
63
                              {SimulationConfig::Run::SpikeLocation::soma, "soma"},
64
                              {SimulationConfig::Run::SpikeLocation::AIS, "AIS"}})
65
NLOHMANN_JSON_SERIALIZE_ENUM(SimulationConfig::Run::IntegrationMethod,
332✔
66
                             {{SimulationConfig::Run::IntegrationMethod::invalid, nullptr},
67
                              {SimulationConfig::Run::IntegrationMethod::euler, 0},
68
                              {SimulationConfig::Run::IntegrationMethod::nicholson, 1},
69
                              {SimulationConfig::Run::IntegrationMethod::nicholson_ion, 2}})
70

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

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

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

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

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

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

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

142
void raiseOnBiophysicalPopulationsErrors(
18✔
143
    const std::unordered_map<std::string, PopulationProperties>& populations) {
144
    for (const auto& it : populations) {
44✔
145
        const auto& population = it.first;
30✔
146
        const auto& properties = it.second;
30✔
147
        if (properties.type == "biophysical") {
30✔
148
            if (properties.morphologiesDir.empty() &&
28✔
149
                properties.alternateMorphologyFormats.empty()) {
2✔
150
                throw SonataError(
151
                    fmt::format("Node population '{}' is defined as 'biophysical' "
2✔
152
                                "but does not define 'morphologies_dir' or "
153
                                "'alternateMorphologyFormats'",
154
                                population));
6✔
155
            } else if (properties.biophysicalNeuronModelsDir.empty()) {
24✔
156
                throw SonataError(
157
                    fmt::format("Node population '{}' is defined as 'biophysical' "
2✔
158
                                "but does not define 'biophysical_neuron_models_dir'",
159
                                population));
6✔
160
            }
161
        }
162
    }
163
}
14✔
164

165
PopulationProperties getPopulationProperties(
30✔
166
    const std::string& populationName,
167
    const std::unordered_map<std::string, PopulationProperties>& populations) {
168
    auto it = populations.find(populationName);
30✔
169
    if (it == populations.end()) {
30✔
170
        throw SonataError(fmt::format("Could not find population '{}'", populationName));
16✔
171
    }
172

173
    return it->second;
22✔
174
}
175

176
template <typename PopulationType>
177
PopulationType getPopulation(const std::string& populationName,
8✔
178
                             const std::unordered_map<std::string, PopulationProperties>& src) {
179
    const auto properties = getPopulationProperties(populationName, src);
12✔
180
    return PopulationType(properties.elementsPath, properties.typesPath, populationName);
8✔
181
}
182

183
std::map<std::string, std::string> replaceVariables(std::map<std::string, std::string> variables) {
104✔
184
    constexpr size_t maxIterations = 10;
104✔
185

186
    bool anyChange = true;
104✔
187
    size_t iteration = 0;
104✔
188

189
    while (anyChange) {
392✔
190
        anyChange = false;
148✔
191
        auto variablesCopy = variables;
296✔
192

193
        for (const auto& vI : variables) {
282✔
194
            const auto& vIKey = vI.first;
134✔
195
            const auto& vIValue = vI.second;
134✔
196

197
            for (auto& vJ : variablesCopy) {
416✔
198
                auto& vJValue = vJ.second;
282✔
199
                const auto startPos = vJValue.find(vIKey);
282✔
200

201
                if (startPos != std::string::npos) {
282✔
202
                    vJValue.replace(startPos, vIKey.length(), vIValue);
70✔
203
                    anyChange = true;
70✔
204
                }
205
            }
206
        }
207
        variables = variablesCopy;
148✔
208

209
        if (++iteration == maxIterations) {
148✔
210
            throw SonataError(
211
                "Reached maximum allowed iterations in variable expansion, "
212
                "possibly infinite recursion.");
4✔
213
        }
214
    }
215

216
    return variables;
100✔
217
}
218

219
nlohmann::json expandVariables(const nlohmann::json& json,
100✔
220
                               const std::map<std::string, std::string>& vars) {
221
    auto jsonFlat = json.flatten();
200✔
222

223
    // Expand variables in whole json
224
    for (auto it = jsonFlat.begin(); it != jsonFlat.end(); ++it) {
1,578✔
225
        auto& value = it.value();
1,478✔
226
        if (!value.is_string()) {
1,478✔
227
            continue;
806✔
228
        }
229

230
        auto valueStr = value.get<std::string>();
1,344✔
231

232
        for (const auto& var : vars) {
1,388✔
233
            const auto& varName = var.first;
716✔
234
            const auto& varValue = var.second;
716✔
235
            const auto startPos = valueStr.find(varName);
716✔
236

237
            if (startPos != std::string::npos) {
716✔
238
                valueStr.replace(startPos, varName.length(), varValue);
86✔
239
                value = fs::path(valueStr).lexically_normal();
86✔
240
            }
241
        }
242
    }
243

244
    return jsonFlat.unflatten();
200✔
245
}
246

247
using Variables = std::map<std::string, std::string>;
248

249
Variables readVariables(const nlohmann::json& json) {
106✔
250
    Variables variables;
106✔
251

252
    if (json.find("manifest") == json.end()) {
106✔
253
        return variables;
66✔
254
    }
255

256
    const auto manifest = json["manifest"];
80✔
257

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

260
    for (auto it = manifest.begin(); it != manifest.end(); ++it) {
96✔
261
        const auto& name = it.key();
58✔
262

263
        if (std::regex_match(name, regexVariable)) {
58✔
264
            variables[name] = it.value();
56✔
265
        } else {
266
            throw SonataError(fmt::format("Invalid variable `{}`", name));
4✔
267
        }
268
    }
269

270
    return variables;
38✔
271
}
272

273
std::string toAbsolute(const fs::path& base, const fs::path& path) {
146✔
274
    const auto absolute = path.is_absolute() ? path : fs::absolute(base / path);
292✔
275
    return absolute.lexically_normal().string();
292✔
276
}
277

278
template <typename Type, typename std::enable_if<std::is_enum<Type>::value>::type* = nullptr>
279
void raiseIfInvalidEnum(const char* name,
350✔
280
                        const Type& buf,
281
                        const std::string& found_value,
282
                        std::true_type /* tag */) {
283
    if (buf == Type::invalid) {
350✔
284
        throw SonataError(fmt::format("Invalid value: '{}' for key '{}'", found_value, name));
32✔
285
    }
286
}
334✔
287

288
template <typename Type>
289
void raiseIfInvalidEnum(const char* /*unused*/,
1,264✔
290
                        const Type& /*unused*/,
291
                        const std::string& /*unused*/,
292
                        std::false_type /* tag */) {}
1,264✔
293

294
template <typename Type>
295
void parseMandatory(const nlohmann::json& it,
1,060✔
296
                    const char* name,
297
                    const std::string& section_name,
298
                    Type& buf) {
299
    const auto element = it.find(name);
1,060✔
300
    if (element == it.end()) {
1,060✔
301
        throw SonataError(fmt::format("Could not find '{}' in '{}'", name, section_name));
64✔
302
    }
303
    buf = element->get<Type>();
1,028✔
304

305
    raiseIfInvalidEnum(name, buf, element->dump(), std::is_enum<Type>());
1,038✔
306
}
1,018✔
307

308
template <typename Type>
309
void parseOptional(const nlohmann::json& it,
1,230✔
310
                   const char* name,
311
                   Type& buf,
312
                   nonstd::optional<Type> default_value = nonstd::nullopt) {
313
    const auto element = it.find(name);
1,230✔
314
    if (element != it.end()) {
1,230✔
315
        buf = element->get<Type>();
586✔
316
        raiseIfInvalidEnum(name, buf, element->dump(), std::is_enum<Type>());
592✔
317
    } else if (default_value != nonstd::nullopt) {
644✔
318
        buf = default_value.value();
602✔
319
    }
320
}
1,224✔
321

322
void parseVariantType(const nlohmann::json& it, variantValueType& var) {
32✔
323
    switch (it.type()) {
32✔
324
    case nlohmann::json::value_t::boolean:
8✔
325
        var = it.get<bool>();
8✔
326
        break;
8✔
327
    case nlohmann::json::value_t::string:
8✔
328
        var = it.get<std::string>();
8✔
329
        break;
8✔
330
    case nlohmann::json::value_t::number_float:
8✔
331
        var = it.get<double>();
8✔
332
        break;
8✔
333
    case nlohmann::json::value_t::number_integer:
8✔
334
    case nlohmann::json::value_t::number_unsigned:
335
        var = it.get<int>();
8✔
336
        break;
8✔
337
    default:
×
338
        throw SonataError("Value type not supported");
×
339
    }
340
}
32✔
341

342
SimulationConfig::Input parseInputModule(const nlohmann::json& valueIt,
68✔
343
                                         const SimulationConfig::InputBase::Module module,
344
                                         const std::string& basePath,
345
                                         int randomSeed,
346
                                         const std::string& debugStr) {
347
    using Module = SimulationConfig::InputBase::Module;
348

349
    const auto parseCommon = [&](auto& input) {
68✔
350
        input.module = module;
68✔
351
        parseMandatory(valueIt, "input_type", debugStr, input.inputType);
266✔
352
        parseMandatory(valueIt, "delay", debugStr, input.delay);
132✔
353
        parseMandatory(valueIt, "duration", debugStr, input.duration);
132✔
354
        parseMandatory(valueIt, "node_set", debugStr, input.nodeSet);
132✔
355
    };
134✔
356

357
    switch (module) {
68✔
358
    case Module::linear: {
6✔
359
        SimulationConfig::InputLinear ret;
12✔
360
        parseCommon(ret);
6✔
361
        parseMandatory(valueIt, "amp_start", debugStr, ret.ampStart);
4✔
362
        parseOptional(valueIt, "amp_end", ret.ampEnd, {ret.ampStart});
4✔
363
        return ret;
4✔
364
    }
365
    case Module::relative_linear: {
4✔
366
        SimulationConfig::InputRelativeLinear ret;
8✔
367
        parseCommon(ret);
4✔
368
        parseMandatory(valueIt, "percent_start", debugStr, ret.percentStart);
4✔
369
        parseOptional(valueIt, "percent_end", ret.percentEnd, {ret.percentStart});
4✔
370
        return ret;
4✔
371
    }
372
    case Module::pulse: {
4✔
373
        SimulationConfig::InputPulse ret;
8✔
374
        parseCommon(ret);
4✔
375
        parseMandatory(valueIt, "amp_start", debugStr, ret.ampStart);
4✔
376
        parseMandatory(valueIt, "width", debugStr, ret.width);
4✔
377
        parseMandatory(valueIt, "frequency", debugStr, ret.frequency);
4✔
378
        parseOptional(valueIt, "amp_end", ret.ampEnd, {ret.ampStart});
4✔
379
        return ret;
4✔
380
    }
381
    case Module::subthreshold: {
4✔
382
        SimulationConfig::InputSubthreshold ret;
8✔
383
        parseCommon(ret);
4✔
384
        parseMandatory(valueIt, "percent_less", debugStr, ret.percentLess);
4✔
385
        return ret;
4✔
386
    }
387
    case Module::noise: {
12✔
388
        SimulationConfig::InputNoise ret;
24✔
389
        parseCommon(ret);
12✔
390
        const auto mean = valueIt.find("mean");
12✔
391
        const auto mean_percent = valueIt.find("mean_percent");
12✔
392

393
        if (mean != valueIt.end()) {
12✔
394
            parseOptional(valueIt, "mean", ret.mean);
12✔
395
        }
396

397
        if (mean_percent != valueIt.end()) {
12✔
398
            parseOptional(valueIt, "mean_percent", ret.meanPercent);
6✔
399
        }
400

401
        if (ret.mean.has_value() && ret.meanPercent.has_value()) {
12✔
402
            throw SonataError("Both `mean` or `mean_percent` have values in " + debugStr);
2✔
403
        } else if (!ret.mean.has_value() && !ret.meanPercent.has_value()) {
10✔
404
            throw SonataError("One of `mean` or `mean_percent` need to have a value in " +
4✔
405
                              debugStr);
6✔
406
        }
407

408
        parseOptional(valueIt, "variance", ret.variance);
8✔
409
        return ret;
8✔
410
    }
411
    case Module::shot_noise: {
4✔
412
        SimulationConfig::InputShotNoise ret;
8✔
413
        parseCommon(ret);
4✔
414
        parseMandatory(valueIt, "rise_time", debugStr, ret.riseTime);
4✔
415
        parseMandatory(valueIt, "decay_time", debugStr, ret.decayTime);
4✔
416
        parseOptional(valueIt, "random_seed", ret.randomSeed, {randomSeed});
4✔
417
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
418
        parseMandatory(valueIt, "rate", debugStr, ret.rate);
4✔
419
        parseMandatory(valueIt, "amp_mean", debugStr, ret.ampMean);
4✔
420
        parseMandatory(valueIt, "amp_var", debugStr, ret.ampVar);
4✔
421
        return ret;
4✔
422
    }
423
    case Module::relative_shot_noise: {
4✔
424
        SimulationConfig::InputRelativeShotNoise ret;
8✔
425
        parseCommon(ret);
4✔
426

427
        parseMandatory(valueIt, "rise_time", debugStr, ret.riseTime);
4✔
428
        parseMandatory(valueIt, "decay_time", debugStr, ret.decayTime);
4✔
429
        parseOptional(valueIt, "random_seed", ret.randomSeed, {randomSeed});
4✔
430
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
431
        parseMandatory(valueIt, "amp_cv", debugStr, ret.ampCv);
4✔
432
        parseMandatory(valueIt, "mean_percent", debugStr, ret.meanPercent);
4✔
433
        parseMandatory(valueIt, "sd_percent", debugStr, ret.sdPercent);
4✔
434
        return ret;
4✔
435
    }
436
    case Module::absolute_shot_noise: {
4✔
437
        SimulationConfig::InputAbsoluteShotNoise ret;
8✔
438
        parseCommon(ret);
4✔
439

440
        parseMandatory(valueIt, "rise_time", debugStr, ret.riseTime);
4✔
441
        parseMandatory(valueIt, "decay_time", debugStr, ret.decayTime);
4✔
442
        parseOptional(valueIt, "random_seed", ret.randomSeed, {randomSeed});
4✔
443
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
444
        parseMandatory(valueIt, "amp_cv", debugStr, ret.ampCv);
4✔
445
        parseMandatory(valueIt, "mean", debugStr, ret.mean);
4✔
446
        parseMandatory(valueIt, "sigma", debugStr, ret.sigma);
4✔
447
        return ret;
4✔
448
    }
449
    case Module::hyperpolarizing: {
6✔
450
        SimulationConfig::InputHyperpolarizing ret;
12✔
451
        parseCommon(ret);
6✔
452
        return ret;
6✔
453
    }
454
    case Module::synapse_replay: {
8✔
455
        SimulationConfig::InputSynapseReplay ret;
16✔
456
        parseCommon(ret);
8✔
457
        parseMandatory(valueIt, "spike_file", debugStr, ret.spikeFile);
8✔
458
        parseOptional(valueIt, "source", ret.source);
8✔
459
        ret.spikeFile = toAbsolute(basePath, ret.spikeFile);
8✔
460
        return ret;
8✔
461
    }
462
    case Module::seclamp: {
4✔
463
        SimulationConfig::InputSeclamp ret;
8✔
464
        parseCommon(ret);
4✔
465
        parseMandatory(valueIt, "voltage", debugStr, ret.voltage);
4✔
466
        parseOptional(valueIt, "series_resistance", ret.seriesResistance, {0.01});
4✔
467
        return ret;
4✔
468
    }
469
    case Module::ornstein_uhlenbeck: {
4✔
470
        SimulationConfig::InputOrnsteinUhlenbeck ret;
8✔
471
        parseCommon(ret);
4✔
472
        parseMandatory(valueIt, "tau", debugStr, ret.tau);
4✔
473
        parseOptional(valueIt, "reversal", ret.reversal);
4✔
474
        parseOptional(valueIt, "random_seed", ret.randomSeed, {randomSeed});
4✔
475
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
476

477
        parseMandatory(valueIt, "mean", debugStr, ret.mean);
4✔
478
        parseMandatory(valueIt, "sigma", debugStr, ret.sigma);
4✔
479
        return ret;
4✔
480
    }
481
    case Module::relative_ornstein_uhlenbeck: {
4✔
482
        SimulationConfig::InputRelativeOrnsteinUhlenbeck ret;
8✔
483
        parseCommon(ret);
4✔
484
        parseMandatory(valueIt, "tau", debugStr, ret.tau);
4✔
485
        parseOptional(valueIt, "reversal", ret.reversal);
4✔
486
        parseOptional(valueIt, "random_seed", ret.randomSeed, {randomSeed});
4✔
487
        parseOptional(valueIt, "dt", ret.dt, {0.25});
4✔
488

489
        parseMandatory(valueIt, "mean_percent", debugStr, ret.meanPercent);
4✔
490
        parseMandatory(valueIt, "sd_percent", debugStr, ret.sdPercent);
4✔
491
        return ret;
4✔
492
    }
493
    default:
×
494
        throw SonataError("Unknown module for the input_type in " + debugStr);
×
495
    }
496
}
497

498
void parseConditionsMechanisms(
4✔
499
    const nlohmann::json& it,
500
    std::unordered_map<std::string, std::unordered_map<std::string, variantValueType>>& buf) {
501
    const auto mechIt = it.find("mechanisms");
4✔
502
    if (mechIt == it.end()) {
4✔
503
        return;
×
504
    }
505
    for (auto& scopeIt : mechIt->items()) {
12✔
506
        std::unordered_map<std::string, variantValueType> map_vars;
16✔
507
        for (auto& varIt : scopeIt.value().items()) {
24✔
508
            variantValueType res_val;
32✔
509
            parseVariantType(varIt.value(), res_val);
16✔
510
            map_vars.insert({varIt.key(), res_val});
16✔
511
        }
512
        buf.insert({scopeIt.key(), map_vars});
8✔
513
    }
514
}
515

516
void parseConditionsModifications(const nlohmann::json& it,
4✔
517
                                  SimulationConfig::ModificationMap& buf) {
518
    const auto sectionIt = it.find("modifications");
4✔
519
    if (sectionIt == it.end()) {
4✔
520
        return;
×
521
    }
522
    for (auto& mIt : sectionIt->items()) {
12✔
523
        const auto valueIt = mIt.value();
16✔
524
        const auto debugStr = fmt::format("modification {}", mIt.key());
16✔
525

526
        SimulationConfig::ModificationBase::ModificationType type;
527
        parseMandatory(valueIt, "type", debugStr, type);
8✔
528

529
        switch (type) {
8✔
530
        case SimulationConfig::ModificationBase::ModificationType::TTX: {
4✔
531
            SimulationConfig::ModificationTTX result;
8✔
532
            result.type = type;
4✔
533
            parseMandatory(valueIt, "node_set", debugStr, result.nodeSet);
4✔
534
            buf[mIt.key()] = result;
4✔
535
            break;
4✔
536
        }
537
        case SimulationConfig::ModificationBase::ModificationType::ConfigureAllSections: {
4✔
538
            SimulationConfig::ModificationConfigureAllSections result;
8✔
539
            result.type = type;
4✔
540
            parseMandatory(valueIt, "node_set", debugStr, result.nodeSet);
4✔
541
            parseMandatory(valueIt, "section_configure", debugStr, result.sectionConfigure);
4✔
542
            buf[mIt.key()] = result;
4✔
543
            break;
4✔
544
        }
545
        default:
×
546
            throw SonataError("Unknown modificationn type in " + debugStr);
×
547
        }
548
    }
549
}
550

551
}  // namespace
552

553
class CircuitConfig::Parser
28✔
554
{
555
  public:
556
    using Subnetworks = std::vector<CircuitConfig::SubnetworkFiles>;
557
    using PopulationOverrides = std::unordered_map<std::string, PopulationProperties>;
558

559
    Parser(const std::string& contents, const std::string& basePath)
34✔
560
        : _basePath(fs::absolute(fs::path(basePath))) {
40✔
561
        // Parse and expand JSON string
562
        const auto rawJson = nlohmann::json::parse(contents);
68✔
563
        const auto vars = replaceVariables(readVariables(rawJson));
66✔
564
        _json = expandVariables(rawJson, vars);
28✔
565
    }
28✔
566

567
    template <typename T>
568
    T getJSONValue(const nlohmann::json& json,
242✔
569
                   const std::string& key,
570
                   const T& defaultValue = T()) const {
571
        auto it = json.find(key);
242✔
572
        if (it != json.end() && !it->is_null()) {
242✔
573
            return it.value().get<T>();
78✔
574
        }
575

576
        return defaultValue;
164✔
577
    }
578

579
    std::string getJSONPath(const nlohmann::json& json,
194✔
580
                            const std::string& key,
581
                            const std::string& defaultValue = std::string()) const {
582
        auto value = getJSONValue<std::string>(json, key);
388✔
583
        if (!value.empty()) {
194✔
584
            return toAbsolute(_basePath, value);
66✔
585
        }
586

587
        return defaultValue;
128✔
588
    }
589

590
    ConfigStatus getCircuitConfigStatus() const {
28✔
591
        if (_json.find("metadata") == _json.end()) {
28✔
592
            return ConfigStatus::complete;
28✔
593
        }
594
        const auto& metadata = _json.at("metadata");
×
595
        const auto res = getJSONValue<ConfigStatus>(metadata, "status", {ConfigStatus::complete});
×
596

597
        if (res == ConfigStatus::invalid) {
×
598
            throw SonataError("Invalid value for `metadata::ConfigStatus` in config");
×
599
        }
600

601
        return res;
×
602
    }
603

604
    nlohmann::json getSubNetworkJson(const std::string& prefix,
50✔
605
                                     CircuitConfig::ConfigStatus ConfigStatus) const {
606
        // Fail if no network entry is defined
607
        if (_json.find("networks") == _json.end()) {
50✔
608
            if (ConfigStatus == CircuitConfig::ConfigStatus::complete) {
2✔
609
                throw SonataError("Error parsing config: `networks` not specified");
2✔
610
            } else {
611
                return {};
×
612
            }
613
        }
614

615
        const auto& networks = _json.at("networks");
48✔
616

617
        const std::string component = prefix + "s";
96✔
618
        if (networks.find(component) == networks.end()) {
48✔
619
            if (ConfigStatus == CircuitConfig::ConfigStatus::complete) {
6✔
620
                throw SonataError(
621
                    fmt::format("Error parsing networks config: '{}' not specified", component));
12✔
622
            } else {
623
                return {};
×
624
            }
625
        }
626

627
        return networks.at(component);
42✔
628
    }
629

630
    std::string getNodeSetsPath() const {
28✔
631
        // Retrieve node sets file, if any
632
        if (_json.find("node_sets_file") != _json.end()) {
28✔
633
            return toAbsolute(_basePath, _json["node_sets_file"]);
8✔
634
        }
635

636
        return {};
20✔
637
    }
638

639
    CircuitConfig::Components parseDefaultComponents() const {
18✔
640
        CircuitConfig::Components result;
18✔
641

642
        if (_json.find("components") == _json.end()) {
18✔
643
            return result;
×
644
        }
645

646
        const auto& components = _json.at("components");
18✔
647

648
        result.morphologiesDir = getJSONPath(components, "morphologies_dir");
18✔
649

650
        const auto alternateMorphoDir = components.find("alternate_morphologies");
18✔
651
        if (alternateMorphoDir != components.end()) {
18✔
652
            for (auto it = alternateMorphoDir->begin(); it != alternateMorphoDir->end(); ++it) {
12✔
653
                result.alternateMorphologiesDir[it.key()] = toAbsolute(_basePath, it.value());
6✔
654
            }
655
        }
656

657
        result.biophysicalNeuronModelsDir = getJSONPath(components,
36✔
658
                                                        "biophysical_neuron_models_dir");
18✔
659

660
        return result;
18✔
661
    }
662

663
    template <typename JSON>
664
    std::tuple<std::string, std::string> parseSubNetworks(const std::string& prefix,
32✔
665
                                                          const JSON& value) const {
666
        const std::string elementsFile = prefix + "s_file";
64✔
667
        auto h5File = getJSONPath(value, elementsFile);
64✔
668

669
        if (h5File.empty()) {
32✔
670
            throw SonataError(
671
                fmt::format("'{}' network do not define '{}' entry", prefix, elementsFile));
4✔
672
        }
673

674
        const std::string typesFile = prefix + "_types_file";
60✔
675
        auto csvFile = getJSONPath(value, typesFile);
60✔
676

677
        return {h5File, csvFile};
60✔
678
    }
679

680
    template <typename Population>
681
    PopulationOverrides parsePopulationProperties(const std::string& prefix,
50✔
682
                                                  CircuitConfig::ConfigStatus status) const {
683
        const auto& network = getSubNetworkJson(prefix, status);
92✔
684

685
        std::unordered_map<std::string, PopulationProperties> output;
42✔
686

687
        for (const auto& subnetwork : network) {
72✔
688
            std::string elementsPath;
60✔
689
            std::string typesPath;
60✔
690
            std::tie(elementsPath, typesPath) = parseSubNetworks(prefix, subnetwork);
32✔
691

692
            const auto populationsIt = subnetwork.find("populations");
30✔
693
            if (populationsIt == subnetwork.end()) {
30✔
694
                continue;
4✔
695
            }
696

697
            for (auto it = populationsIt->begin(); it != populationsIt->end(); ++it) {
74✔
698
                const auto& popData = it.value();
48✔
699

700
                if (output.find(it.key()) != output.end()) {
48✔
701
                    throw SonataError(fmt::format("Population {} is declared twice", it.key()));
×
702
                }
703

704
                PopulationProperties& popProperties = output[it.key()];
48✔
705

706
                popProperties.elementsPath = elementsPath;
48✔
707
                popProperties.typesPath = typesPath;
48✔
708

709
                popProperties.type = getJSONValue<std::string>(popData, "type");
48✔
710
                popProperties.morphologiesDir = getJSONPath(popData, "morphologies_dir");
48✔
711
                popProperties.biophysicalNeuronModelsDir =
48✔
712
                    getJSONPath(popData, "biophysical_neuron_models_dir");
713

714
                // Overwrite those specified, if any
715
                const auto altMorphoDir = popData.find("alternate_morphologies");
48✔
716
                if (altMorphoDir != popData.end()) {
48✔
717
                    for (auto it = altMorphoDir->begin(); it != altMorphoDir->end(); ++it) {
8✔
718
                        popProperties.alternateMorphologyFormats[it.key()] = toAbsolute(_basePath,
4✔
719
                                                                                        it.value());
4✔
720
                    }
721
                }
722
            }
723
        }
724

725
        return output;
80✔
726
    }
727

728
    PopulationOverrides parseNodePopulations(CircuitConfig::ConfigStatus status) const {
28✔
729
        return parsePopulationProperties<NodePopulation>("node", status);
28✔
730
    }
731

732
    PopulationOverrides parseEdgePopulations(CircuitConfig::ConfigStatus status) const {
22✔
733
        return parsePopulationProperties<EdgePopulation>("edge", status);
22✔
734
    }
735

736
    std::string getExpandedJSON() const {
28✔
737
        return _json.dump();
28✔
738
    }
739

740
  private:
741
    const fs::path _basePath;
742
    nlohmann::json _json;
743
};
744

745
CircuitConfig::CircuitConfig(const std::string& contents, const std::string& basePath) {
54✔
746
    Parser parser(contents, basePath);
62✔
747

748
    _expandedJSON = parser.getExpandedJSON();
28✔
749
    _status = parser.getCircuitConfigStatus();
28✔
750

751
    _nodeSetsFile = parser.getNodeSetsPath();
28✔
752

753
    // Load node population overrides and check biophysical types
754
    _nodePopulationProperties = parser.parseNodePopulations(_status);
28✔
755

756
    // Load edge population overrides
757
    _edgePopulationProperties = parser.parseEdgePopulations(_status);
22✔
758

759
    Components defaultComponents = parser.parseDefaultComponents();
36✔
760

761
    const auto updateDefaultProperties =
762
        [&](std::unordered_map<std::string, PopulationProperties>& map,
763
            const std::string& defaultType) {
36✔
764
            for (auto& entry : map) {
84✔
765
                auto& component = entry.second;
48✔
766
                if (component.type.empty()) {
48✔
767
                    component.type = defaultType;
40✔
768
                }
769

770
                if (component.alternateMorphologyFormats.empty()) {
48✔
771
                    component.alternateMorphologyFormats =
44✔
772
                        defaultComponents.alternateMorphologiesDir;
134✔
773
                }
774

775
                if (component.biophysicalNeuronModelsDir.empty()) {
48✔
776
                    component.biophysicalNeuronModelsDir =
46✔
777
                        defaultComponents.biophysicalNeuronModelsDir;
46✔
778
                }
779

780
                if (component.morphologiesDir.empty()) {
48✔
781
                    component.morphologiesDir = defaultComponents.morphologiesDir;
44✔
782
                }
783
            }
784
        };
54✔
785

786
    updateDefaultProperties(_nodePopulationProperties, "biophysical");
18✔
787
    updateDefaultProperties(_edgePopulationProperties, "chemical");
18✔
788

789
    if (getCircuitConfigStatus() == ConfigStatus::complete) {
18✔
790
        raiseOnBiophysicalPopulationsErrors(_nodePopulationProperties);
18✔
791
    }
792
}
14✔
793

794
CircuitConfig CircuitConfig::fromFile(const std::string& path) {
14✔
795
    return CircuitConfig(readFile(path), fs::path(path).parent_path());
14✔
796
}
797

798
CircuitConfig::ConfigStatus CircuitConfig::getCircuitConfigStatus() const {
18✔
799
    return _status;
18✔
800
}
801

802
const std::string& CircuitConfig::getNodeSetsPath() const {
10✔
803
    return _nodeSetsFile;
10✔
804
}
805

806
std::set<std::string> CircuitConfig::listNodePopulations() const {
2✔
807
    return getMapKeys(_nodePopulationProperties);
2✔
808
}
809

810
NodePopulation CircuitConfig::getNodePopulation(const std::string& name) const {
4✔
811
    return getPopulation<NodePopulation>(name, _nodePopulationProperties);
4✔
812
}
813

814
std::set<std::string> CircuitConfig::listEdgePopulations() const {
2✔
815
    return getMapKeys(_edgePopulationProperties);
2✔
816
}
817

818
EdgePopulation CircuitConfig::getEdgePopulation(const std::string& name) const {
4✔
819
    return getPopulation<EdgePopulation>(name, _edgePopulationProperties);
4✔
820
}
821

822
PopulationProperties CircuitConfig::getNodePopulationProperties(const std::string& name) const {
12✔
823
    return getPopulationProperties(name, _nodePopulationProperties);
12✔
824
}
825

826
PopulationProperties CircuitConfig::getEdgePopulationProperties(const std::string& name) const {
10✔
827
    return getPopulationProperties(name, _edgePopulationProperties);
10✔
828
}
829

830
const std::string& CircuitConfig::getExpandedJSON() const {
4✔
831
    return _expandedJSON;
4✔
832
}
833

834
class SimulationConfig::Parser
72✔
835
{
836
  public:
837
    Parser(const std::string& content, const std::string& basePath)
72✔
838
        : _basePath(fs::absolute(fs::path(basePath)).lexically_normal()) {
72✔
839
        // Parse manifest section and expand JSON string
840
        const auto rawJson = nlohmann::json::parse(content);
144✔
841
        const auto vars = replaceVariables(readVariables(rawJson));
144✔
842
        _json = expandVariables(rawJson, vars);
72✔
843
    }
72✔
844

845
    SimulationConfig::Run parseRun() const {
140✔
846
        const auto runIt = _json.find("run");
140✔
847
        if (runIt == _json.end()) {
140✔
848
            throw SonataError("Could not find 'run' section");
2✔
849
        }
850

851
        SimulationConfig::Run result{};
138✔
852
        parseMandatory(*runIt, "tstop", "run", result.tstop);
140✔
853
        parseMandatory(*runIt, "dt", "run", result.dt);
138✔
854
        parseMandatory(*runIt, "random_seed", "run", result.randomSeed);
150✔
855
        parseOptional(*runIt, "spike_threshold", result.spikeThreshold, {-30});
118✔
856
        parseOptional(*runIt, "spike_location", result.spikeLocation, {Run::SpikeLocation::soma});
120✔
857
        parseOptional(*runIt,
118✔
858
                      "integration_method",
859
                      result.integrationMethod,
860
                      {Run::IntegrationMethod::euler});
861
        parseOptional(*runIt, "stimulus_seed", result.stimulusSeed, {0});
114✔
862
        parseOptional(*runIt, "ionchannel_seed", result.ionchannelSeed, {0});
114✔
863
        parseOptional(*runIt, "minis_seed", result.minisSeed, {0});
114✔
864
        parseOptional(*runIt, "synapse_seed", result.synapseSeed, {0});
114✔
865
        return result;
114✔
866
    }
867

868
    SimulationConfig::Output parseOutput() const {
46✔
869
        SimulationConfig::Output result{};
46✔
870

871
        const auto outputIt = _json.find("output");
46✔
872
        if (outputIt == _json.end()) {
46✔
873
            return result;
42✔
874
        }
875
        parseOptional(*outputIt, "output_dir", result.outputDir, {"output"});
4✔
876
        parseOptional(*outputIt, "log_file", result.logFile, {""});
4✔
877
        parseOptional(*outputIt, "spikes_file", result.spikesFile, {"out.h5"});
4✔
878
        parseOptional(*outputIt,
4✔
879
                      "spikes_sort_order",
880
                      result.sortOrder,
881
                      {Output::SpikesSortOrder::by_time});
882

883
        result.outputDir = toAbsolute(_basePath, result.outputDir);
4✔
884

885
        return result;
4✔
886
    }
887

888
    SimulationConfig::Conditions parseConditions() const {
46✔
889
        SimulationConfig::Conditions result{};
46✔
890

891
        const auto conditionsIt = _json.find("conditions");
46✔
892
        if (conditionsIt == _json.end()) {
46✔
893
            return result;
42✔
894
        }
895
        parseOptional(*conditionsIt, "celsius", result.celsius, {34.0});
4✔
896
        parseOptional(*conditionsIt, "v_init", result.vInit, {-80});
4✔
897
        parseOptional(*conditionsIt,
4✔
898
                      "synapses_init_depleted",
899
                      result.synapsesInitDepleted,
900
                      {false});
901
        parseOptional(*conditionsIt, "extracellular_calcium", result.extracellularCalcium);
4✔
902
        parseOptional(*conditionsIt,
4✔
903
                      "randomize_gaba_rise_time",
904
                      result.randomizeGabaRiseTime,
905
                      {false});
906
        parseConditionsMechanisms(*conditionsIt, result.mechanisms);
4✔
907
        parseConditionsModifications(*conditionsIt, result.modifications);
4✔
908
        return result;
4✔
909
    }
910

911
    ReportMap parseReports(const SimulationConfig::Output& output) const {
46✔
912
        ReportMap result;
46✔
913

914
        const auto reportsIt = _json.find("reports");
46✔
915
        if (reportsIt == _json.end()) {
46✔
916
            return result;
22✔
917
        }
918

919
        for (auto it = reportsIt->begin(); it != reportsIt->end(); ++it) {
40✔
920
            auto& report = result[it.key()];
36✔
921
            const auto& valueIt = it.value();
36✔
922
            const std::string debugStr = "report " + it.key();
72✔
923

924
            parseOptional(valueIt, "cells", report.cells, parseNodeSet());
36✔
925
            parseOptional(valueIt, "sections", report.sections, {Report::Sections::soma});
36✔
926
            parseMandatory(valueIt, "type", debugStr, report.type);
36✔
927
            parseOptional(valueIt, "scaling", report.scaling, {Report::Scaling::area});
32✔
928
            parseOptional(valueIt,
32✔
929
                          "compartments",
930
                          report.compartments,
931
                          {report.sections == Report::Sections::soma ? Report::Compartments::center
64✔
932
                                                                     : Report::Compartments::all});
933
            parseMandatory(valueIt, "variable_name", debugStr, report.variableName);
32✔
934
            parseOptional(valueIt, "unit", report.unit, {"mV"});
30✔
935
            parseMandatory(valueIt, "dt", debugStr, report.dt);
30✔
936
            parseMandatory(valueIt, "start_time", debugStr, report.startTime);
28✔
937
            parseMandatory(valueIt, "end_time", debugStr, report.endTime);
26✔
938
            parseOptional(valueIt, "file_name", report.fileName, {it.key() + "_SONATA.h5"});
24✔
939
            parseOptional(valueIt, "enabled", report.enabled, {true});
24✔
940

941
            // variable names can look like:
942
            // `v`, or `i_clamp`, or `Foo.bar` but not `..asdf`, or `asdf..` or `asdf.asdf.asdf`
943
            const char* const varName = R"(\w+(?:\.?\w+)?)";
24✔
944
            // variable names are separated by `,` with any amount of whitespace separating them
945
            const std::regex expr(fmt::format(R"({}(?:\s*,\s*{})*)", varName, varName));
48✔
946
            if (!std::regex_match(report.variableName, expr)) {
24✔
947
                throw SonataError(fmt::format("Invalid comma separated variable names '{}'",
16✔
948
                                              report.variableName));
24✔
949
            }
950

951
            const auto extension = fs::path(report.fileName).extension().string();
32✔
952
            if (extension.empty() || extension != ".h5") {
16✔
953
                report.fileName += ".h5";
12✔
954
            }
955
            report.fileName = toAbsolute(output.outputDir, report.fileName);
16✔
956
        }
957

958
        return result;
4✔
959
    }
960

961
    std::string parseNetwork() const {
34✔
962
        auto val = _json.value("network", "circuit_config.json");
68✔
963
        return toAbsolute(_basePath, val);
68✔
964
    }
965

966
    SimulationConfig::SimulatorType parseTargetSimulator() const {
10✔
967
        SimulationConfig::SimulatorType val;
968
        parseOptional(_json, "target_simulator", val, {SimulationConfig::SimulatorType::NEURON});
12✔
969
        return val;
8✔
970
    }
971

972
    std::string parseNodeSetsFile() const noexcept {
8✔
973
        std::string val;
16✔
974
        if (_json.contains("node_sets_file")) {
8✔
975
            val = _json["node_sets_file"];
×
976
            return toAbsolute(_basePath, val);
×
977
        } else {
978
            try {
979
                const auto circuitFile = parseNetwork();
16✔
980
                const auto conf = CircuitConfig::fromFile(circuitFile);
12✔
981
                return conf.getNodeSetsPath();
4✔
982
            } catch (...) {
8✔
983
                // Don't throw CircuitConfig exceptions in SimulationConfig and return empty string
984
                return val;
4✔
985
            }
986
        }
987
    }
988

989
    nonstd::optional<std::string> parseNodeSet() const {
44✔
990
        if (_json.contains("node_set")) {
44✔
991
            return {_json["node_set"]};
20✔
992
        } else {
993
            return nonstd::nullopt;
24✔
994
        }
995
    }
996

997
    InputMap parseInputs() const {
26✔
998
        InputMap result;
26✔
999

1000
        const auto inputsIt = _json.find("inputs");
26✔
1001
        if (inputsIt == _json.end()) {
26✔
1002
            return result;
8✔
1003
        }
1004

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

1009
            InputBase::Module module;
1010
            parseMandatory(valueIt, "module", debugStr, module);
74✔
1011

1012
            const auto input =
1013
                parseInputModule(valueIt, module, _basePath, parseRun().randomSeed, debugStr);
136✔
1014
            result[it.key()] = input;
62✔
1015

1016
            auto mismatchingModuleInputType = [&it]() {
4✔
1017
                const auto module_name = it->find("module")->get<std::string>();
4✔
1018
                const auto input_type = it->find("input_type")->get<std::string>();
4✔
1019
                throw SonataError(
1020
                    fmt::format("An `input` has module `{}` and input_type `{}` which mismatch",
2✔
1021
                                module_name,
1022
                                input_type));
6✔
1023
            };
62✔
1024

1025
            auto inputType = nonstd::visit([](const auto& v) { return v.inputType; }, input);
124✔
1026
            switch (inputType) {
62✔
1027
            case InputBase::InputType::current_clamp: {
40✔
1028
                if (!(nonstd::holds_alternative<SimulationConfig::InputLinear>(input) ||
80✔
1029
                      nonstd::holds_alternative<SimulationConfig::InputRelativeLinear>(input) ||
68✔
1030
                      nonstd::holds_alternative<SimulationConfig::InputPulse>(input) ||
60✔
1031
                      nonstd::holds_alternative<SimulationConfig::InputSubthreshold>(input) ||
52✔
1032
                      nonstd::holds_alternative<SimulationConfig::InputNoise>(input) ||
40✔
1033
                      nonstd::holds_alternative<SimulationConfig::InputShotNoise>(input) ||
28✔
1034
                      nonstd::holds_alternative<SimulationConfig::InputRelativeShotNoise>(input) ||
20✔
1035
                      nonstd::holds_alternative<SimulationConfig::InputAbsoluteShotNoise>(input) ||
16✔
1036
                      nonstd::holds_alternative<SimulationConfig::InputHyperpolarizing>(input) ||
12✔
1037
                      nonstd::holds_alternative<SimulationConfig::InputOrnsteinUhlenbeck>(input) ||
4✔
1038
                      nonstd::holds_alternative<SimulationConfig::InputRelativeOrnsteinUhlenbeck>(
4✔
1039
                          input))) {
1040
                    mismatchingModuleInputType();
×
1041
                }
1042
            } break;
40✔
1043
            case InputBase::InputType::spikes:
4✔
1044
                if (!nonstd::holds_alternative<SimulationConfig::InputSynapseReplay>(input)) {
4✔
1045
                    mismatchingModuleInputType();
×
1046
                }
1047
                break;
4✔
1048
            case InputBase::InputType::voltage_clamp:
6✔
1049
                if (!nonstd::holds_alternative<SimulationConfig::InputSeclamp>(input)) {
6✔
1050
                    mismatchingModuleInputType();
2✔
1051
                }
1052
                break;
4✔
1053
            case InputBase::InputType::extracellular_stimulation:
4✔
1054
                break;
4✔
1055
            case InputBase::InputType::conductance:
8✔
1056
                if (!(nonstd::holds_alternative<SimulationConfig::InputShotNoise>(input) ||
16✔
1057
                      nonstd::holds_alternative<SimulationConfig::InputRelativeShotNoise>(input) ||
16✔
1058
                      nonstd::holds_alternative<SimulationConfig::InputAbsoluteShotNoise>(input) ||
12✔
1059
                      nonstd::holds_alternative<SimulationConfig::InputOrnsteinUhlenbeck>(input) ||
4✔
1060
                      nonstd::holds_alternative<SimulationConfig::InputRelativeOrnsteinUhlenbeck>(
×
1061
                          input))) {
1062
                    mismatchingModuleInputType();
×
1063
                }
1064
                break;
8✔
1065
            default:
×
1066
                throw SonataError(fmt::format("Unknown input_type in {}", debugStr));
×
1067
            }
1068
        }
1069
        return result;
4✔
1070
    }
1071

1072
    ConnectionMap parseConnectionOverrides() const {
12✔
1073
        ConnectionMap result;
12✔
1074

1075
        const auto connIt = _json.find("connection_overrides");
12✔
1076
        if (connIt == _json.end())
12✔
1077
            return result;
6✔
1078

1079
        for (auto it = connIt->begin(); it != connIt->end(); ++it) {
14✔
1080
            auto& connect = result[it.key()];
10✔
1081
            auto& valueIt = it.value();
10✔
1082
            const auto debugStr = fmt::format("connection_override {}", it.key());
20✔
1083
            parseMandatory(valueIt, "source", debugStr, connect.source);
10✔
1084
            parseMandatory(valueIt, "target", debugStr, connect.target);
10✔
1085
            parseOptional(valueIt, "weight", connect.weight);
8✔
1086
            parseOptional(valueIt, "spont_minis", connect.spontMinis);
8✔
1087
            parseOptional(valueIt, "synapse_configure", connect.synapseConfigure);
8✔
1088
            parseOptional(valueIt, "modoverride", connect.modoverride);
8✔
1089
            parseOptional(valueIt, "synapse_delay_override", connect.synapseDelayOverride);
8✔
1090
            parseOptional(valueIt, "delay", connect.delay);
8✔
1091
            parseOptional(valueIt, "neuromodulation_dtc", connect.neuromodulationDtc);
8✔
1092
            parseOptional(valueIt, "neuromodulation_strength", connect.neuromodulationStrength);
8✔
1093
        }
1094
        return result;
4✔
1095
    }
1096

1097
    std::unordered_map<std::string, std::string> parseMetaData() const {
8✔
1098
        std::unordered_map<std::string, std::string> result;
8✔
1099
        const auto metaIt = _json.find("metadata");
8✔
1100
        if (metaIt == _json.end())
8✔
1101
            return result;
4✔
1102
        for (auto& it : metaIt->items()) {
12✔
1103
            result.insert({it.key(), it.value()});
8✔
1104
        }
1105
        return result;
4✔
1106
    }
1107

1108
    std::unordered_map<std::string, variantValueType> parseBetaFeatures() const {
8✔
1109
        std::unordered_map<std::string, variantValueType> result;
8✔
1110
        const auto fIt = _json.find("beta_features");
8✔
1111
        if (fIt == _json.end())
8✔
1112
            return result;
4✔
1113
        for (auto& it : fIt->items()) {
20✔
1114
            variantValueType res_val;
32✔
1115
            parseVariantType(it.value(), res_val);
16✔
1116
            result.insert({it.key(), res_val});
16✔
1117
        }
1118
        return result;
4✔
1119
    }
1120

1121
    std::string getExpandedJSON() const {
72✔
1122
        return _json.dump();
72✔
1123
    }
1124

1125
  private:
1126
    const fs::path _basePath;
1127
    nlohmann::json _json;
1128
};
1129

1130
SimulationConfig::SimulationConfig(const std::string& content, const std::string& basePath)
72✔
1131
    : _basePath(fs::absolute(basePath).lexically_normal().string()) {
136✔
1132
    const Parser parser(content, basePath);
144✔
1133
    _expandedJSON = parser.getExpandedJSON();
72✔
1134
    _run = parser.parseRun();
72✔
1135
    _output = parser.parseOutput();
46✔
1136
    _conditions = parser.parseConditions();
46✔
1137
    _reports = parser.parseReports(_output);
46✔
1138
    _network = parser.parseNetwork();
26✔
1139
    _inputs = parser.parseInputs();
26✔
1140
    _connections = parser.parseConnectionOverrides();
12✔
1141
    _targetSimulator = parser.parseTargetSimulator();
10✔
1142
    _nodeSetsFile = parser.parseNodeSetsFile();
8✔
1143
    _nodeSet = parser.parseNodeSet();
8✔
1144
    _metaData = parser.parseMetaData();
8✔
1145
    _betaFeatures = parser.parseBetaFeatures();
8✔
1146
}
8✔
1147

1148
SimulationConfig SimulationConfig::fromFile(const std::string& path) {
4✔
1149
    return SimulationConfig(readFile(path), fs::path(path).parent_path());
4✔
1150
}
1151

1152
const std::string& SimulationConfig::getBasePath() const noexcept {
2✔
1153
    return _basePath;
2✔
1154
}
1155

1156
const SimulationConfig::Run& SimulationConfig::getRun() const noexcept {
30✔
1157
    return _run;
30✔
1158
}
1159

1160
const SimulationConfig::Output& SimulationConfig::getOutput() const noexcept {
12✔
1161
    return _output;
12✔
1162
}
1163

1164
const SimulationConfig::Conditions& SimulationConfig::getConditions() const noexcept {
28✔
1165
    return _conditions;
28✔
1166
}
1167

1168
const std::string& SimulationConfig::getNetwork() const noexcept {
6✔
1169
    return _network;
6✔
1170
}
1171

1172
std::set<std::string> SimulationConfig::listReportNames() const {
2✔
1173
    return getMapKeys(_reports);
2✔
1174
}
1175

1176
const SimulationConfig::Report& SimulationConfig::getReport(const std::string& name) const {
30✔
1177
    const auto it = _reports.find(name);
30✔
1178
    if (it == _reports.end()) {
30✔
1179
        throw SonataError(
1180
            fmt::format("The report '{}' is not present in the simulation config file", name));
4✔
1181
    }
1182

1183
    return it->second;
28✔
1184
}
1185

1186
std::set<std::string> SimulationConfig::listInputNames() const {
2✔
1187
    return getMapKeys(_inputs);
2✔
1188
}
1189

1190
const SimulationConfig::Input& SimulationConfig::getInput(const std::string& name) const {
30✔
1191
    const auto it = _inputs.find(name);
30✔
1192
    if (it == _inputs.end()) {
30✔
1193
        throw SonataError(
1194
            fmt::format("The input '{}' is not present in the simulation config file", name));
4✔
1195
    }
1196
    return it->second;
28✔
1197
}
1198

1199
std::set<std::string> SimulationConfig::listConnectionOverrideNames() const {
2✔
1200
    return getMapKeys(_connections);
2✔
1201
}
1202

1203
const SimulationConfig::ConnectionOverride& SimulationConfig::getConnectionOverride(
34✔
1204
    const std::string& name) const {
1205
    const auto it = _connections.find(name);
34✔
1206
    if (it == _connections.end()) {
34✔
1207
        throw SonataError(
1208
            fmt::format("The connection_override '{}' is not present in the simulation config file",
×
1209
                        name));
×
1210
    }
1211
    return it->second;
34✔
1212
}
1213

1214
const SimulationConfig::SimulatorType& SimulationConfig::getTargetSimulator() const {
4✔
1215
    return _targetSimulator;
4✔
1216
}
1217

1218
const std::string& SimulationConfig::getNodeSetsFile() const noexcept {
4✔
1219
    return _nodeSetsFile;
4✔
1220
}
1221

1222
const nonstd::optional<std::string>& SimulationConfig::getNodeSet() const noexcept {
4✔
1223
    return _nodeSet;
4✔
1224
}
1225

1226
const std::unordered_map<std::string, std::string>& SimulationConfig::getMetaData() const noexcept {
2✔
1227
    return _metaData;
2✔
1228
}
1229

1230
const std::unordered_map<std::string, variantValueType>& SimulationConfig::getBetaFeatures() const
2✔
1231
    noexcept {
1232
    return _betaFeatures;
2✔
1233
}
1234

1235
const std::string& SimulationConfig::getExpandedJSON() const {
2✔
1236
    return _expandedJSON;
2✔
1237
}
1238

1239
std::set<std::string> SimulationConfig::Conditions::listModificationNames() const {
2✔
1240
    return getMapKeys(modifications);
2✔
1241
}
1242

1243
const SimulationConfig::Modification& SimulationConfig::Conditions::getModification(
6✔
1244
    const std::string& name) const {
1245
    const auto it = modifications.find(name);
6✔
1246
    if (it == modifications.end()) {
6✔
1247
        throw SonataError(
1248
            fmt::format("The modification '{}' is not present in the simulation config file",
2✔
1249
                        name));
6✔
1250
    }
1251
    return it->second;
4✔
1252
}
1253

1254
}  // namespace sonata
1255
}  // namespace bbp
90✔
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