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

BlueBrain / libsonata / 5737317549

pending completion
5737317549

push

github

jorblancoa
Remove get_array()

1796 of 1865 relevant lines covered (96.3%)

77.2 hits per line

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

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

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

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

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

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

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

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

53
namespace bbp {
54
namespace sonata {
55

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

225
        for (const auto& vI : variables) {
286✔
226
            const auto& vIKey = vI.first;
138✔
227
            const auto& vIValue = vI.second;
138✔
228

229
            for (auto& vJ : variablesCopy) {
432✔
230
                auto& vJValue = vJ.second;
294✔
231
                const auto startPos = vJValue.find(vIKey);
294✔
232

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

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

248
    return variables;
100✔
249
}
250

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

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

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

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

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

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

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

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

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

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

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

292
    for (auto it = manifest.begin(); it != manifest.end(); ++it) {
100✔
293
        const auto& name = it.key();
62✔
294

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

302
    return variables;
38✔
303
}
304

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

560
        SimulationConfig::ModificationBase::ModificationType type;
561
        parseMandatory(valueIt, "type", debugStr, type);
8✔
562

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

585
}  // namespace
586

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

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

607
        return defaultValue;
408✔
608
    }
609

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

617
        return nonstd::nullopt;
212✔
618
    }
619

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

628
        return defaultValue;
160✔
629
    }
630

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

638
        if (res == ConfigStatus::invalid) {
×
639
            throw SonataError("Invalid value for `metadata::ConfigStatus` in config");
×
640
        }
641

642
        return res;
×
643
    }
644

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

656
        const auto& networks = _json.at("networks");
48✔
657

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

668
        return networks.at(component);
42✔
669
    }
670

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

677
        return {};
20✔
678
    }
679

680
    CircuitConfig::Components parseDefaultComponents() const {
18✔
681
        CircuitConfig::Components result;
18✔
682

683
        if (_json.find("components") == _json.end()) {
18✔
684
            return result;
×
685
        }
686

687
        const auto& components = _json.at("components");
18✔
688

689
        result.morphologiesDir = getJSONPath(components, "morphologies_dir");
18✔
690

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

698
        result.biophysicalNeuronModelsDir = getJSONPath(components,
36✔
699
                                                        "biophysical_neuron_models_dir");
18✔
700

701
        result.vasculatureFile = getOptionalJSONPath(components, "vasculature_file");
18✔
702
        result.vasculatureMesh = getOptionalJSONPath(components, "vasculature_mesh");
18✔
703
        result.endfeetMeshesFile = getOptionalJSONPath(components, "endfeet_meshes_file");
18✔
704
        result.microdomainsFile = getOptionalJSONPath(components, "microdomains_file");
18✔
705
        result.spineMorphologiesDir = getOptionalJSONPath(components, "spine_morphologies_dir");
18✔
706

707
        return result;
18✔
708
    }
709

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

718
        if (component.alternateMorphologyFormats.empty()) {
48✔
719
            component.alternateMorphologyFormats = defaultComponents.alternateMorphologiesDir;
44✔
720
        }
721

722
        if (component.biophysicalNeuronModelsDir.empty()) {
48✔
723
            component.biophysicalNeuronModelsDir = defaultComponents.biophysicalNeuronModelsDir;
46✔
724
        }
725

726
        if (component.morphologiesDir.empty()) {
48✔
727
            component.morphologiesDir = defaultComponents.morphologiesDir;
44✔
728
        }
729
    }
48✔
730

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

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

742
            if (!component.vasculatureMesh) {
30✔
743
                component.vasculatureMesh = defaultComponents.vasculatureMesh;
30✔
744
            }
745

746
            if (!component.microdomainsFile) {
30✔
747
                component.microdomainsFile = defaultComponents.microdomainsFile;
30✔
748
            }
749
            if (!component.spineMorphologiesDir) {
30✔
750
                component.spineMorphologiesDir = defaultComponents.spineMorphologiesDir;
30✔
751
            }
752
        }
753
    }
18✔
754

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

762
            if (!component.endfeetMeshesFile) {
18✔
763
                component.endfeetMeshesFile = defaultComponents.endfeetMeshesFile;
18✔
764
            }
765
        }
766
    }
18✔
767

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

774
        if (h5File.empty()) {
32✔
775
            throw SonataError(
776
                fmt::format("'{}' network do not define '{}' entry", prefix, elementsFile));
4✔
777
        }
778

779
        const std::string typesFile = prefix + "_types_file";
60✔
780
        auto csvFile = getJSONPath(value, typesFile);
60✔
781

782
        return {h5File, csvFile};
60✔
783
    }
784

785
    template <typename PopulationProperties, typename Func>
786
    std::unordered_map<std::string, PopulationProperties> parsePopulationProperties(
50✔
787
        const std::string& prefix, CircuitConfig::ConfigStatus status, const Func& parser) const {
788
        const auto& network = getSubNetworkJson(prefix, status);
92✔
789

790
        std::unordered_map<std::string, PopulationProperties> output;
42✔
791

792
        for (const auto& subnetwork : network) {
102✔
793
            std::string elementsPath;
34✔
794
            std::string typesPath;
34✔
795
            std::tie(elementsPath, typesPath) = parseSubNetworks(prefix, subnetwork);
32✔
796

797
            const auto populationsIt = subnetwork.find("populations");
30✔
798
            if (populationsIt == subnetwork.end()) {
30✔
799
                continue;
4✔
800
            }
801

802
            for (auto it = populationsIt->begin(); it != populationsIt->end(); ++it) {
74✔
803
                const auto& popData = it.value();
48✔
804

805
                if (output.find(it.key()) != output.end()) {
48✔
806
                    throw SonataError(fmt::format("Population {} is declared twice", it.key()));
×
807
                }
808

809
                PopulationProperties& popProperties = output[it.key()];
48✔
810

811
                popProperties.elementsPath = elementsPath;
48✔
812
                popProperties.typesPath = typesPath;
48✔
813

814
                popProperties.type = getJSONValue<std::string>(popData, "type");
48✔
815
                popProperties.morphologiesDir = getJSONPath(popData, "morphologies_dir");
48✔
816
                popProperties.biophysicalNeuronModelsDir =
48✔
817
                    getJSONPath(popData, "biophysical_neuron_models_dir");
818

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

828
                parser(popProperties, popData);
48✔
829
            }
830
        }
831

832
        return output;
80✔
833
    }
834

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

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

861
    std::string getExpandedJSON() const {
28✔
862
        return _json.dump();
28✔
863
    }
864

865
  private:
866
    const fs::path _basePath;
867
    nlohmann::json _json;
868
};
869

870
CircuitConfig::CircuitConfig(const std::string& contents, const std::string& basePath) {
114✔
871
    Parser parser(contents, basePath);
62✔
872

873
    _expandedJSON = parser.getExpandedJSON();
28✔
874
    _status = parser.getCircuitConfigStatus();
28✔
875

876
    _nodeSetsFile = parser.getNodeSetsPath();
28✔
877

878
    _nodePopulationProperties = parser.parseNodePopulations(_status);
28✔
879
    _edgePopulationProperties = parser.parseEdgePopulations(_status);
22✔
880

881
    Components defaultComponents = parser.parseDefaultComponents();
36✔
882

883
    parser.updateDefaultNodeProperties(_nodePopulationProperties, "biophysical", defaultComponents);
18✔
884
    parser.updateDefaultEdgeProperties(_edgePopulationProperties, "chemical", defaultComponents);
18✔
885

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

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

909
CircuitConfig CircuitConfig::fromFile(const std::string& path) {
14✔
910
    return CircuitConfig(readFile(path), fs::path(path).parent_path());
34✔
911
}
912

913
CircuitConfig::ConfigStatus CircuitConfig::getCircuitConfigStatus() const {
18✔
914
    return _status;
18✔
915
}
916

917
const std::string& CircuitConfig::getNodeSetsPath() const {
10✔
918
    return _nodeSetsFile;
10✔
919
}
920

921
std::set<std::string> CircuitConfig::listNodePopulations() const {
2✔
922
    return getMapKeys(_nodePopulationProperties);
2✔
923
}
924

925
NodePopulation CircuitConfig::getNodePopulation(const std::string& name) const {
4✔
926
    return getPopulation<NodePopulation>(name, _nodePopulationProperties);
4✔
927
}
928

929
std::set<std::string> CircuitConfig::listEdgePopulations() const {
2✔
930
    return getMapKeys(_edgePopulationProperties);
2✔
931
}
932

933
EdgePopulation CircuitConfig::getEdgePopulation(const std::string& name) const {
4✔
934
    return getPopulation<EdgePopulation>(name, _edgePopulationProperties);
4✔
935
}
936

937
NodePopulationProperties CircuitConfig::getNodePopulationProperties(const std::string& name) const {
12✔
938
    return getPopulationProperties(name, _nodePopulationProperties);
12✔
939
}
940

941
EdgePopulationProperties CircuitConfig::getEdgePopulationProperties(const std::string& name) const {
10✔
942
    return getPopulationProperties(name, _edgePopulationProperties);
10✔
943
}
944

945
const std::string& CircuitConfig::getExpandedJSON() const {
4✔
946
    return _expandedJSON;
4✔
947
}
948

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

960
    SimulationConfig::Run parseRun() const {
72✔
961
        const auto runIt = _json.find("run");
72✔
962
        if (runIt == _json.end()) {
72✔
963
            throw SonataError("Could not find 'run' section");
2✔
964
        }
965

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

981
        if (!result.electrodesFile.empty()) {
48✔
982
            result.electrodesFile = toAbsolute(_basePath, result.electrodesFile);
4✔
983
        }
984

985
        return result;
96✔
986
    }
987

988
    SimulationConfig::Output parseOutput() const {
48✔
989
        SimulationConfig::Output result{};
48✔
990

991
        const auto outputIt = _json.find("output");
48✔
992
        if (outputIt == _json.end()) {
48✔
993
            return result;
44✔
994
        }
995
        parseOptional(*outputIt, "output_dir", result.outputDir, {"output"});
4✔
996
        parseOptional(*outputIt, "log_file", result.logFile, {""});
4✔
997
        parseOptional(*outputIt, "spikes_file", result.spikesFile, {"out.h5"});
4✔
998
        parseOptional(*outputIt,
4✔
999
                      "spikes_sort_order",
1000
                      result.sortOrder,
4✔
1001
                      {Output::SpikesSortOrder::by_time});
4✔
1002

1003
        result.outputDir = toAbsolute(_basePath, result.outputDir);
4✔
1004

1005
        return result;
4✔
1006
    }
1007

1008
    SimulationConfig::Conditions parseConditions() const {
48✔
1009
        SimulationConfig::Conditions result{};
48✔
1010

1011
        const auto conditionsIt = _json.find("conditions");
48✔
1012
        if (conditionsIt == _json.end()) {
48✔
1013
            return result;
42✔
1014
        }
1015
        parseOptional(*conditionsIt, "celsius", result.celsius, {34.0});
6✔
1016
        parseOptional(*conditionsIt, "v_init", result.vInit, {-80});
6✔
1017
        parseOptional(*conditionsIt,
8✔
1018
                      "spike_location",
1019
                      result.spikeLocation,
6✔
1020
                      {Conditions::SpikeLocation::soma});
8✔
1021
        parseOptional(*conditionsIt, "extracellular_calcium", result.extracellularCalcium);
4✔
1022
        parseOptional(*conditionsIt,
8✔
1023
                      "randomize_gaba_rise_time",
1024
                      result.randomizeGabaRiseTime,
4✔
1025
                      {false});
1026
        parseConditionsMechanisms(*conditionsIt, result.mechanisms);
4✔
1027
        parseConditionsModifications(*conditionsIt, result.modifications);
4✔
1028
        return result;
4✔
1029
    }
1030

1031
    ReportMap parseReports(const SimulationConfig::Output& output) const {
46✔
1032
        ReportMap result;
46✔
1033

1034
        const auto reportsIt = _json.find("reports");
46✔
1035
        if (reportsIt == _json.end()) {
46✔
1036
            return result;
22✔
1037
        }
1038

1039
        for (auto it = reportsIt->begin(); it != reportsIt->end(); ++it) {
44✔
1040
            auto& report = result[it.key()];
40✔
1041
            const auto& valueIt = it.value();
40✔
1042
            const std::string debugStr = "report " + it.key();
80✔
1043

1044
            parseOptional(valueIt, "cells", report.cells, parseNodeSet());
40✔
1045
            parseOptional(valueIt, "sections", report.sections, {Report::Sections::soma});
40✔
1046
            parseMandatory(valueIt, "type", debugStr, report.type);
40✔
1047
            parseOptional(valueIt, "scaling", report.scaling, {Report::Scaling::area});
36✔
1048
            parseOptional(valueIt,
36✔
1049
                          "compartments",
1050
                          report.compartments,
36✔
1051
                          {report.sections == Report::Sections::soma ? Report::Compartments::center
36✔
1052
                                                                     : Report::Compartments::all});
1053
            parseMandatory(valueIt, "variable_name", debugStr, report.variableName);
36✔
1054
            parseOptional(valueIt, "unit", report.unit, {"mV"});
34✔
1055
            parseMandatory(valueIt, "dt", debugStr, report.dt);
34✔
1056
            parseMandatory(valueIt, "start_time", debugStr, report.startTime);
32✔
1057
            parseMandatory(valueIt, "end_time", debugStr, report.endTime);
30✔
1058
            parseOptional(valueIt, "file_name", report.fileName, {it.key() + ".h5"});
28✔
1059
            parseOptional(valueIt, "enabled", report.enabled, {true});
28✔
1060

1061
            // variable names can look like:
1062
            // `v`, or `i_clamp`, or `Foo.bar` but not `..asdf`, or `asdf..` or `asdf.asdf.asdf`
1063
            const char* const varName = R"(\w+(?:\.?\w+)?)";
28✔
1064
            // variable names are separated by `,` with any amount of whitespace separating them
1065
            const std::regex expr(fmt::format(R"({}(?:\s*,\s*{})*)", varName, varName));
56✔
1066
            if (!std::regex_match(report.variableName, expr)) {
28✔
1067
                throw SonataError(fmt::format("Invalid comma separated variable names '{}'",
8✔
1068
                                              report.variableName));
24✔
1069
            }
1070

1071
            const auto extension = fs::path(report.fileName).extension().string();
40✔
1072
            if (extension.empty() || extension != ".h5") {
20✔
1073
                report.fileName += ".h5";
8✔
1074
            }
1075
            report.fileName = toAbsolute(output.outputDir, report.fileName);
20✔
1076
        }
1077

1078
        return result;
4✔
1079
    }
1080

1081
    std::string parseNetwork() const {
34✔
1082
        auto val = _json.value("network", "circuit_config.json");
68✔
1083
        return toAbsolute(_basePath, val);
68✔
1084
    }
1085

1086
    SimulationConfig::SimulatorType parseTargetSimulator() const {
10✔
1087
        SimulationConfig::SimulatorType val;
1088
        parseOptional(_json, "target_simulator", val, {SimulationConfig::SimulatorType::NEURON});
12✔
1089
        return val;
8✔
1090
    }
1091

1092
    std::string parseNodeSetsFile() const noexcept {
8✔
1093
        std::string val;
16✔
1094
        if (_json.contains("node_sets_file")) {
8✔
1095
            val = _json["node_sets_file"];
×
1096
            return toAbsolute(_basePath, val);
×
1097
        } else {
1098
            try {
1099
                const auto circuitFile = parseNetwork();
16✔
1100
                const auto conf = CircuitConfig::fromFile(circuitFile);
12✔
1101
                return conf.getNodeSetsPath();
4✔
1102
            } catch (...) {
4✔
1103
                // Don't throw CircuitConfig exceptions in SimulationConfig and return empty string
1104
                return val;
4✔
1105
            }
1106
        }
1107
    }
1108

1109
    nonstd::optional<std::string> parseNodeSet() const {
48✔
1110
        if (_json.contains("node_set")) {
48✔
1111
            return {_json["node_set"]};
24✔
1112
        } else {
1113
            return nonstd::nullopt;
24✔
1114
        }
1115
    }
1116

1117
    InputMap parseInputs() const {
26✔
1118
        InputMap result;
26✔
1119

1120
        const auto inputsIt = _json.find("inputs");
26✔
1121
        if (inputsIt == _json.end()) {
26✔
1122
            return result;
8✔
1123
        }
1124

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

1129
            InputBase::Module module;
1130
            parseMandatory(valueIt, "module", debugStr, module);
74✔
1131

1132
            const auto input = parseInputModule(valueIt, module, _basePath, debugStr);
136✔
1133
            result[it.key()] = input;
62✔
1134

1135
            auto mismatchingModuleInputType = [&it]() {
4✔
1136
                const auto module_name = it->find("module")->get<std::string>();
4✔
1137
                const auto input_type = it->find("input_type")->get<std::string>();
4✔
1138
                throw SonataError(
1139
                    fmt::format("An `input` has module `{}` and input_type `{}` which mismatch",
2✔
1140
                                module_name,
1141
                                input_type));
6✔
1142
            };
62✔
1143

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

1191
    std::vector<ConnectionOverride> parseConnectionOverrides() const {
12✔
1192
        std::vector<ConnectionOverride> result;
12✔
1193

1194
        const auto connIt = _json.find("connection_overrides");
12✔
1195
        // nlohmann::json::flatten().unflatten() converts empty containers to `null`:
1196
        // https://json.nlohmann.me/api/basic_json/unflatten/#notes
1197
        // so we can't tell the difference between {} and []; however, since these are
1198
        // empty, we will assume the intent was to have no connection_overrides and forgo
1199
        // better error reporting
1200
        if (connIt == _json.end() || connIt->is_null()) {
12✔
1201
            return result;
6✔
1202
        }
1203

1204
        if (!connIt->is_array()) {
6✔
1205
            throw SonataError("`connection_overrides` must be an array");
2✔
1206
        }
1207

1208
        result.reserve(connIt->size());
4✔
1209

1210
        for (auto it = connIt->begin(); it != connIt->end(); ++it) {
12✔
1211
            const auto& valueIt = it.value();
8✔
1212
            ConnectionOverride connect;
16✔
1213
            parseMandatory(valueIt, "name", "connection_override", connect.name);
8✔
1214
            const auto debugStr = fmt::format("connection_override {}", connect.name);
8✔
1215
            parseMandatory(valueIt, "source", debugStr, connect.source);
8✔
1216
            parseMandatory(valueIt, "target", debugStr, connect.target);
8✔
1217
            parseOptional(valueIt, "weight", connect.weight);
8✔
1218
            parseOptional(valueIt, "spont_minis", connect.spontMinis);
8✔
1219
            parseOptional(valueIt, "synapse_configure", connect.synapseConfigure);
8✔
1220
            parseOptional(valueIt, "modoverride", connect.modoverride);
8✔
1221
            parseOptional(valueIt, "synapse_delay_override", connect.synapseDelayOverride);
8✔
1222
            parseOptional(valueIt, "delay", connect.delay);
8✔
1223
            parseOptional(valueIt, "neuromodulation_dtc", connect.neuromodulationDtc);
8✔
1224
            parseOptional(valueIt, "neuromodulation_strength", connect.neuromodulationStrength);
8✔
1225

1226
            result.push_back(std::move(connect));
8✔
1227
        }
1228
        return result;
4✔
1229
    }
1230

1231
    std::unordered_map<std::string, variantValueType> parseMetaData() const {
8✔
1232
        std::unordered_map<std::string, variantValueType> result;
8✔
1233
        const auto metaIt = _json.find("metadata");
8✔
1234
        if (metaIt == _json.end()) {
8✔
1235
            return result;
4✔
1236
        }
1237
        for (auto& it : metaIt->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::unordered_map<std::string, variantValueType> parseBetaFeatures() const {
8✔
1246
        std::unordered_map<std::string, variantValueType> result;
8✔
1247
        const auto fIt = _json.find("beta_features");
8✔
1248
        if (fIt == _json.end()) {
8✔
1249
            return result;
4✔
1250
        }
1251
        for (auto& it : fIt->items()) {
20✔
1252
            variantValueType res_val;
16✔
1253
            parseVariantType(it.value(), res_val);
16✔
1254
            result.insert({it.key(), res_val});
16✔
1255
        }
1256
        return result;
4✔
1257
    }
1258

1259
    std::string getExpandedJSON() const {
72✔
1260
        return _json.dump();
72✔
1261
    }
1262

1263
  private:
1264
    const fs::path _basePath;
1265
    nlohmann::json _json;
1266
};
1267

1268
SimulationConfig::SimulationConfig(const std::string& content, const std::string& basePath)
72✔
1269
    : _basePath(fs::absolute(basePath).lexically_normal().string()) {
904✔
1270
    const Parser parser(content, basePath);
136✔
1271
    _expandedJSON = parser.getExpandedJSON();
72✔
1272
    _run = parser.parseRun();
72✔
1273
    _output = parser.parseOutput();
48✔
1274
    _conditions = parser.parseConditions();
48✔
1275
    _reports = parser.parseReports(_output);
46✔
1276
    _network = parser.parseNetwork();
26✔
1277
    _inputs = parser.parseInputs();
26✔
1278
    _connection_overrides = parser.parseConnectionOverrides();
12✔
1279
    _targetSimulator = parser.parseTargetSimulator();
10✔
1280
    _nodeSetsFile = parser.parseNodeSetsFile();
8✔
1281
    _nodeSet = parser.parseNodeSet();
8✔
1282
    _metaData = parser.parseMetaData();
8✔
1283
    _betaFeatures = parser.parseBetaFeatures();
8✔
1284
}
8✔
1285

1286
SimulationConfig SimulationConfig::fromFile(const std::string& path) {
4✔
1287
    return SimulationConfig(readFile(path), fs::path(path).parent_path());
8✔
1288
}
1289

1290
const std::string& SimulationConfig::getBasePath() const noexcept {
2✔
1291
    return _basePath;
2✔
1292
}
1293

1294
const SimulationConfig::Run& SimulationConfig::getRun() const noexcept {
32✔
1295
    return _run;
32✔
1296
}
1297

1298
const SimulationConfig::Output& SimulationConfig::getOutput() const noexcept {
14✔
1299
    return _output;
14✔
1300
}
1301

1302
const SimulationConfig::Conditions& SimulationConfig::getConditions() const noexcept {
28✔
1303
    return _conditions;
28✔
1304
}
1305

1306
const std::string& SimulationConfig::getNetwork() const noexcept {
6✔
1307
    return _network;
6✔
1308
}
1309

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

1314
const SimulationConfig::Report& SimulationConfig::getReport(const std::string& name) const {
34✔
1315
    const auto it = _reports.find(name);
34✔
1316
    if (it == _reports.end()) {
34✔
1317
        throw SonataError(
1318
            fmt::format("The report '{}' is not present in the simulation config file", name));
4✔
1319
    }
1320

1321
    return it->second;
32✔
1322
}
1323

1324
std::set<std::string> SimulationConfig::listInputNames() const {
2✔
1325
    return getMapKeys(_inputs);
2✔
1326
}
1327

1328
const SimulationConfig::Input& SimulationConfig::getInput(const std::string& name) const {
30✔
1329
    const auto it = _inputs.find(name);
30✔
1330
    if (it == _inputs.end()) {
30✔
1331
        throw SonataError(
1332
            fmt::format("The input '{}' is not present in the simulation config file", name));
4✔
1333
    }
1334
    return it->second;
28✔
1335
}
1336

1337
const std::vector<SimulationConfig::ConnectionOverride>& SimulationConfig::getConnectionOverrides()
2✔
1338
    const noexcept {
1339
    return _connection_overrides;
2✔
1340
}
1341

1342
const SimulationConfig::SimulatorType& SimulationConfig::getTargetSimulator() const {
4✔
1343
    return _targetSimulator;
4✔
1344
}
1345

1346
const std::string& SimulationConfig::getNodeSetsFile() const noexcept {
4✔
1347
    return _nodeSetsFile;
4✔
1348
}
1349

1350
const nonstd::optional<std::string>& SimulationConfig::getNodeSet() const noexcept {
4✔
1351
    return _nodeSet;
4✔
1352
}
1353

1354
const std::unordered_map<std::string, variantValueType>& SimulationConfig::getMetaData() const
2✔
1355
    noexcept {
1356
    return _metaData;
2✔
1357
}
1358

1359
const std::unordered_map<std::string, variantValueType>& SimulationConfig::getBetaFeatures() const
2✔
1360
    noexcept {
1361
    return _betaFeatures;
2✔
1362
}
1363

1364
const std::string& SimulationConfig::getExpandedJSON() const {
2✔
1365
    return _expandedJSON;
2✔
1366
}
1367

1368
std::set<std::string> SimulationConfig::Conditions::listModificationNames() const {
2✔
1369
    return getMapKeys(modifications);
2✔
1370
}
1371

1372
const SimulationConfig::Modification& SimulationConfig::Conditions::getModification(
6✔
1373
    const std::string& name) const {
1374
    const auto it = modifications.find(name);
6✔
1375
    if (it == modifications.end()) {
6✔
1376
        throw SonataError(
1377
            fmt::format("The modification '{}' is not present in the simulation config file",
2✔
1378
                        name));
6✔
1379
    }
1380
    return it->second;
4✔
1381
}
1382

1383
}  // namespace sonata
1384
}  // 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