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

BlueBrain / libsonata / 5320571995

pending completion
5320571995

push

github

mgeplf
cleanup; comply w/ copyright statements

1786 of 1854 relevant lines covered (96.33%)

74.76 hits per line

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

93.64
/src/config.cpp
1
/*************************************************************************
2
 * Copyright (C) 2018-2021 Blue Brain Project
3
 *
4
 * This file is part of 'libsonata', distributed under the terms
5
 * of the GNU Lesser General Public License version 3.
6
 *
7
 * See top-level COPYING.LESSER and COPYING files for details.
8
 *************************************************************************/
9

10
#include <bbp/sonata/config.h>
11
#include <bbp/sonata/optional.hpp>
12

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

21
#include <fmt/format.h>
22
#include <nlohmann/json.hpp>
23

24
#include "../extlib/filesystem.hpp"
25
#include "population.hpp"
26
#include "utils.h"
27

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

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

51
namespace bbp {
52
namespace sonata {
53

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

202
    return it->second;
44✔
203
}
204

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

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

215
    bool anyChange = true;
104✔
216
    size_t iteration = 0;
104✔
217

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

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

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

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

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

245
    return variables;
100✔
246
}
247

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

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

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

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

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

273
    return jsonFlat.unflatten();
200✔
274
}
275

276
using Variables = std::map<std::string, std::string>;
277

278
Variables readVariables(const nlohmann::json& json) {
106✔
279
    Variables variables;
106✔
280

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

285
    const auto manifest = json["manifest"];
80✔
286

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

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

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

299
    return variables;
38✔
300
}
301

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

579
}  // namespace
580

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

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

601
        return defaultValue;
408✔
602
    }
603

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

611
        return nonstd::nullopt;
212✔
612
    }
613

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

622
        return defaultValue;
160✔
623
    }
624

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

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

636
        return res;
×
637
    }
638

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

650
        const auto& networks = _json.at("networks");
48✔
651

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

662
        return networks.at(component);
42✔
663
    }
664

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

671
        return {};
20✔
672
    }
673

674
    CircuitConfig::Components parseDefaultComponents() const {
18✔
675
        CircuitConfig::Components result;
18✔
676

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

681
        const auto& components = _json.at("components");
18✔
682

683
        result.morphologiesDir = getJSONPath(components, "morphologies_dir");
18✔
684

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

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

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

701
        return result;
18✔
702
    }
703

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

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

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

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

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

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

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

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

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

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

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

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

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

776
        return {h5File, csvFile};
60✔
777
    }
778

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

784
        std::unordered_map<std::string, PopulationProperties> output;
42✔
785

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

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

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

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

803
                PopulationProperties& popProperties = output[it.key()];
48✔
804

805
                popProperties.elementsPath = elementsPath;
48✔
806
                popProperties.typesPath = typesPath;
48✔
807

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

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

822
                parser(popProperties, popData);
48✔
823
            }
824
        }
825

826
        return output;
80✔
827
    }
828

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

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

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

859
  private:
860
    const fs::path _basePath;
861
    nlohmann::json _json;
862
};
863

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

867
    _expandedJSON = parser.getExpandedJSON();
28✔
868
    _status = parser.getCircuitConfigStatus();
28✔
869

870
    _nodeSetsFile = parser.getNodeSetsPath();
28✔
871

872
    _nodePopulationProperties = parser.parseNodePopulations(_status);
28✔
873
    _edgePopulationProperties = parser.parseEdgePopulations(_status);
22✔
874

875
    Components defaultComponents = parser.parseDefaultComponents();
36✔
876

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

976
    SimulationConfig::Output parseOutput() const {
48✔
977
        SimulationConfig::Output result{};
48✔
978

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

991
        result.outputDir = toAbsolute(_basePath, result.outputDir);
4✔
992

993
        return result;
4✔
994
    }
995

996
    SimulationConfig::Conditions parseConditions() const {
48✔
997
        SimulationConfig::Conditions result{};
48✔
998

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

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

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

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

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

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

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

1066
        return result;
4✔
1067
    }
1068

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

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

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

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

1105
    InputMap parseInputs() const {
26✔
1106
        InputMap result;
26✔
1107

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

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

1117
            InputBase::Module module;
1118
            parseMandatory(valueIt, "module", debugStr, module);
74✔
1119

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

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

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

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

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

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

1196
        result.reserve(connIt->size());
4✔
1197

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

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

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

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

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

1249
  private:
1250
    const fs::path _basePath;
1251
    nlohmann::json _json;
1252
};
1253

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

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

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

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

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

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

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

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

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

1307
    return it->second;
30✔
1308
}
1309

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

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

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

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

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

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

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

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

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

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

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

1368
}  // namespace sonata
1369
}  // 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