-
Notifications
You must be signed in to change notification settings - Fork 13
Parsing report SimulationConfig fields #194
Changes from all commits
c027331
29cd6ce
0b32180
7483916
d5c6b2b
80fae08
3bac0f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
*************************************************************************/ | ||
|
||
#include <bbp/sonata/config.h> | ||
#include <bbp/sonata/optional.hpp> | ||
|
||
#include <algorithm> // transform | ||
#include <cassert> | ||
|
@@ -129,6 +130,20 @@ std::string toAbsolute(const fs::path& base, const fs::path& path) { | |
return absolute.lexically_normal().string(); | ||
} | ||
|
||
template <typename Type> | ||
bool is_in(const Type& value, const std::initializer_list<Type>& lst) { | ||
return (std::find(std::begin(lst), std::end(lst), value) != std::end(lst)); | ||
} | ||
|
||
template <typename Type> | ||
void checkValidField(const Type& field, const std::initializer_list<Type>& possibleValues) { | ||
if (!is_in<std::string>(field, possibleValues)) { | ||
throw SonataError(fmt::format("Field '{}' not supported ('{}') possible", | ||
field, | ||
fmt::join(possibleValues, ", "))); | ||
} | ||
} | ||
|
||
} // namespace | ||
|
||
class CircuitConfig::Parser | ||
|
@@ -500,11 +515,17 @@ class SimulationConfig::Parser | |
buf = element->template get<Type>(); | ||
} | ||
|
||
template <typename Iterator, typename Type> | ||
void parseOptional(const Iterator& it, const char* name, Type& buf) const { | ||
template <typename Type, typename Iterator> | ||
void parseOptional(const Iterator& it, | ||
const char* name, | ||
Type& buf, | ||
nonstd::optional<Type> default_value = nonstd::nullopt) const { | ||
const auto element = it.find(name); | ||
if (element != it.end()) | ||
if (element != it.end()) { | ||
buf = element->template get<Type>(); | ||
} else if (default_value != nonstd::nullopt) { | ||
buf = default_value.value(); | ||
} | ||
} | ||
|
||
SimulationConfig::Run parseRun() const { | ||
|
@@ -546,17 +567,39 @@ class SimulationConfig::Parser | |
auto& valueIt = it.value(); | ||
const auto debugStr = fmt::format("report {}", it.key()); | ||
parseMandatory(valueIt, "cells", debugStr, report.cells); | ||
parseOptional<std::string>(valueIt, "sections", report.sections, "soma"); | ||
parseMandatory(valueIt, "type", debugStr, report.type); | ||
parseOptional<std::string>(valueIt, "scaling", report.scaling, "area"); | ||
if (report.sections == "soma") { | ||
parseOptional<std::string>(valueIt, "compartments", report.compartments, "center"); | ||
} else { | ||
parseOptional<std::string>(valueIt, "compartments", report.compartments, "all"); | ||
} | ||
parseMandatory(valueIt, "variable_name", debugStr, report.variableName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for |
||
parseOptional<std::string>(valueIt, "unit", report.unit, "mV"); | ||
parseMandatory(valueIt, "dt", debugStr, report.dt); | ||
parseMandatory(valueIt, "start_time", debugStr, report.startTime); | ||
parseMandatory(valueIt, "end_time", debugStr, report.endTime); | ||
parseOptional(valueIt, "file_name", report.fileName); | ||
if (report.fileName.empty()) | ||
report.fileName = it.key() + "_SONATA.h5"; | ||
else { | ||
const auto extension = fs::path(report.fileName).extension().string(); | ||
if (extension.empty() || extension != ".h5") | ||
report.fileName += ".h5"; | ||
parseOptional<std::string>(valueIt, | ||
"file_name", | ||
report.fileName, | ||
it.key() + "_SONATA.h5"); | ||
parseOptional(valueIt, "enabled", report.enabled); | ||
|
||
checkValidField<std::string>(report.sections, {"soma", "axon", "dend", "apic", "all"}); | ||
checkValidField<std::string>(report.scaling, {"none", "area"}); | ||
checkValidField<std::string>(report.compartments, {"center", "all"}); | ||
|
||
// Validate comma separated strings | ||
std::regex expr("^\\w+(\\s*,\\s*\\w+)*$"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC, the The grouping can be non-capturing, since we don't extract results. Using a raw string literal will also mean the double backslashes aren't needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets close then this PR and continue the conversation in #197 |
||
if (!std::regex_match(report.variableName, expr)) { | ||
throw SonataError(fmt::format("Invalid comma separated variable names '{}'", | ||
report.variableName)); | ||
} | ||
|
||
const auto extension = fs::path(report.fileName).extension().string(); | ||
if (extension.empty() || extension != ".h5") { | ||
report.fileName += ".h5"; | ||
} | ||
report.fileName = toAbsolute(_basePath, report.fileName); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have everything that looks like an
enum
be an enum, rather than using strings? I'm used to stringly typed things from python, but in c++ I think the style leans towards stronger typing.@jorblancoa I see you were following the style of
type
above, but this is a good chance to change it if we want to.@NadirRoGue do you have a feeling about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only concern is that since Neurodamus needs a string at the end, we will need to convert the enum to the string either in libsonata or later in neurodamus...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you point me to where neurodamus would need a string? The enums would be exposed, so being able to access them and compare that way should work.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If feasible, the enum would be less prone to mistakes and IDE-friendly, IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course is possible, but the code would need to be changed in several places in Neurodamus/CoreNEURON
few examples:
https://bbpgitlab.epfl.ch/hpc/sim/neurodamus-py/-/blob/main/neurodamus/node.py#L884
https://bbpgitlab.epfl.ch/hpc/sim/neurodamus-py/-/blob/main/neurodamus/target_manager.py#L464
https://bbpgitlab.epfl.ch/hpc/sim/neurodamus-core/-/blob/main/hoc/Report.hoc#L86
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was hoping there was something we could do that's more elegant - that seems error prone; is there a way we could put something in the hoc scope that would automatically have these values populated?
iirc,
CoreNEURON
useslibsonata
, so it would be able to take advantage of the the enums that were created there, so it would be safe to serialize only the enum int value.Yeah, one has to write a
toString
/fromString
; it's annoying boiler plate, but it's pretty much the standard for dealing with enums in C++ afaik.The tradeoff I'm seeing is that we have to balance the current convenience of using only
string
with getting the safety of having enums, and having to do some refactoring in other locations. The latter could bring additional benefits, but those are hard to quantify at the moment. Am I reading that right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes exactly.
I created a draft PR also with some proof of concept on how it will look with enums
#197
I think is quite a bit of work in some places (specially hoc as we discussed) on top of having quite a bit extra lines of code aswell in libsonata.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the #197 looks like the right way to go - NLOHMANN_JSON_SERIALIZE_ENUM is a great find, didn't know about it.
I don't mind the extra lines in libsonata, especially if it gains us correctness and maintainability.
IIRC,
pybind11
gives us__str__
for enums, so the initial burden of change over should be ok.If it's ok with you, I'll take that PR, and augment it. In the meantime, I think we should be close to getting this in.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about this, however by default if you convert the enum to string you will get:
I was trying to overload the
__str__
in the bindings to return just "compartment" or "soma", but is not as easy at it seemspybind/pybind11#2537
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the enum value
.name
should work, no?