@@ -133,6 +133,7 @@ std::string GetRelativeFilePath(const std::string &origin_path, const std::strin
133
133
// For each token, split by slash (may contain backslashes)
134
134
for ( const auto token : tokens ){
135
135
// For each sub-token, split by backslash
136
+ // TODO for windows only?
136
137
for ( const auto new_token : string_split ( token, " \\ " ) ){
137
138
new_tokens.push_back (new_token);
138
139
}
@@ -1052,6 +1053,13 @@ bool Model::GetLemsQuantityPathType(const Network &net, const Simulation::LemsQu
1052
1053
1053
1054
return GetLemsQuantityPathType_InputInstance (path.input , input, type, dimension);
1054
1055
}
1056
+ // experimental extensions
1057
+ else if (path.type == Simulation::LemsQuantityPath::DATAREADER){
1058
+ const Network::TimeSeriesReader &reader = net.data_readers .get (path.reader .read_seq );
1059
+ const Network::TimeSeriesReader::InputColumn &column = reader.columns .get (path.reader .colu_seq );
1060
+ type = NamespaceThing::STATE; dimension = column.dimension ;
1061
+ return true ;
1062
+ }
1055
1063
else if (path.type == Simulation::LemsQuantityPath::NETWORK){
1056
1064
// not supported yet!
1057
1065
return false ;
@@ -1217,6 +1225,11 @@ bool Model::LemsQuantityPathToString(const Network &net, const Simulation::LemsQ
1217
1225
const InputSource &input = input_sources.get (list.component );
1218
1226
return LemsQuantityPathToString (input, path.input , ret);
1219
1227
}
1228
+ // experimental extensions
1229
+ else if (type == Path::DATAREADER){
1230
+ ret += net.data_readers .getName (path.reader .read_seq ) + (" [" +accurate_string (path.reader .inst_seq )+" ]/" ) + net.data_readers .get (path.reader .read_seq ).columns .getName (path.reader .colu_seq );
1231
+ return true ;
1232
+ }
1220
1233
else {
1221
1234
printf (" path to string: type %d not supported yet\n " , (int )type);
1222
1235
return false ;
@@ -1372,6 +1385,7 @@ bool Model::ParseLemsSegmentLocator(const ILogProxy &log, const std::vector<std:
1372
1385
if ( morph.segments .size () == 1 ){
1373
1386
// set it to default
1374
1387
segment_id = 0 ;
1388
+ path.fractionAlong = 0.5 ;
1375
1389
}
1376
1390
else {
1377
1391
std::string complaint = " target path needs segment ID, because cell has multiple segments. Setting to implicit default: segment ID = 0" ;
@@ -1382,6 +1396,7 @@ bool Model::ParseLemsSegmentLocator(const ILogProxy &log, const std::vector<std:
1382
1396
// TODO perhaps stop complaining after warning too many times
1383
1397
1384
1398
segment_id = 0 ;
1399
+ path.fractionAlong = 0.5 ;
1385
1400
}
1386
1401
}
1387
1402
@@ -1863,6 +1878,42 @@ bool Model::ParseLemsQuantityPath(const ILogProxy &log, const char *qty_str, con
1863
1878
return ParseLemsQuantityPath_SynapticComponent (log, syn, tokens, path.synapse , tokens_consumed);
1864
1879
// FIXME record a synapse to make sure it works
1865
1880
}
1881
+ // experimental extensions
1882
+ else if ( (group_seq = net.data_readers .get_id (sId )) >= 0 ){
1883
+ path.type = Simulation::LemsQuantityPath::DATAREADER;
1884
+ const Network::TimeSeriesReader &reader = net.data_readers .get (group_seq);
1885
+
1886
+ path.reader .read_seq = group_seq;
1887
+ if (!ParseLemsGroupLocator (log, tokens, " data reader" , net.data_readers , [](const auto &reader, Int id){return ( 0 <= id && id < reader.instances ) ? id : -1 ;}, path.reader .read_seq , path.reader .inst_seq , tokens_consumed)) return false ;
1888
+ // TODO add shortcut without group locator for single instance?
1889
+
1890
+ // now branch according to property type
1891
+ if (!(tokens_consumed+1 <= (Int)tokens.size ())){
1892
+ // if the elements have just one property, its name can be skipped
1893
+ if (reader.columns .size () == 1 ){
1894
+ path.reader .colu_seq = 0 ;
1895
+ return true ;
1896
+ }
1897
+ else {
1898
+ log.error (" incomplete path for datareader element" );
1899
+ return false ;
1900
+ }
1901
+ }
1902
+ else {
1903
+ const char *sProp = tokens[tokens_consumed].c_str (); tokens_consumed++;
1904
+ if (!(tokens_consumed == (Int)tokens.size ())){
1905
+ log.error (" path for datareader element too large" );
1906
+ return false ;
1907
+ }
1908
+
1909
+ path.reader .colu_seq = reader.columns .get_id (sProp );
1910
+ if (path.reader .colu_seq < 0 ){
1911
+ log.error (" property %s not found in datareader %s" , sProp , net.data_readers .getName (group_seq));
1912
+ return false ;
1913
+ }
1914
+ return true ;
1915
+ }
1916
+ }
1866
1917
// parse them for upcoming refs
1867
1918
// and get type!
1868
1919
// FIXME fractionAlong for transmission !
@@ -2514,6 +2565,24 @@ bool ParseAcrossSegOrSegGroup(const ImportLogger &log, const pugi::xml_node &eAp
2514
2565
}
2515
2566
}
2516
2567
2568
+ // get the scaling factor that applies, compared to native units, for that unit name
2569
+ bool ValidateGetUnits (const ILogProxy &log, const DimensionSet &dimensions, const Dimension &dimension, const char *unit_name, LemsUnit &units){
2570
+ if (!dimensions.Has (dimension)){
2571
+ log.error (" there are no specified %s units" , dimensions.Stringify (dimension).c_str () );
2572
+ return false ;
2573
+ }
2574
+ for ( auto scale : dimensions.GetUnits (dimension) ){
2575
+ if (strcmp (unit_name, scale.name .c_str ()) == 0 ){
2576
+ units = scale;
2577
+ return true ;
2578
+ }
2579
+ }
2580
+ // unit name not found in list!
2581
+ std::string known_list; for ( auto scale : dimensions.GetUnits (dimension) ) known_list += " " , known_list += scale.name ;
2582
+ log.error (" unknown units: %s for %s (supported:%s)" , unit_name, dimensions.Stringify (dimension).c_str (), known_list.c_str () );
2583
+ return false ;
2584
+ }
2585
+
2517
2586
// NeuroML physical quantities consist of a numeric, along with an unit name (such as meter, kilometer, etc.) qualifying the quantity the numeric represents. So NeuroML reader code has to check the unit name, to properly read the quantity.
2518
2587
template <typename UnitType>
2519
2588
bool ParseQuantity (const ImportLogger &log, const pugi::xml_node &eLocation, const char *attr_name, Real &num){
@@ -2599,26 +2668,29 @@ bool ParseLemsQuantity(const ImportLogger &log, const pugi::xml_node &eLocation,
2599
2668
return false ;
2600
2669
}
2601
2670
// then get the scaling factor that applies, compared to native units, for that unit name
2602
- if (!dimensions.Has (dimension)){
2603
- log.error (eLocation, " there are no specified %s units for attribute %s" , dimensions.Stringify (dimension).c_str (), attr_name );
2671
+ LemsUnit scale = dimensions.GetNative (Dimension::Unity ());
2672
+ if (!ValidateGetUnits (LogWithElement (log,eLocation), dimensions, dimension, unit_name, scale)) return false ;
2673
+ num = scale.ConvertTo ( pure_number, dimensions.GetNative (dimension) );
2674
+ // printf("valll %f\n",num);
2675
+ return true ;
2676
+ }
2677
+
2678
+ bool ParseDimensionAttribute (const ImportLogger &log, const pugi::xml_node &eTag, const DimensionSet &dimensions, Dimension &dimension, const char *sAttributeName = " dimension" ){
2679
+ auto sDimension = eTag.attribute (sAttributeName ).value ();
2680
+ if (!*sDimension ){
2681
+ log.error (eTag, " %s attribute missing" , sAttributeName ); // TODO RequiredAttribute ?
2604
2682
return false ;
2605
2683
}
2606
- for ( auto scale : dimensions.GetUnits (dimension) ){
2607
- if (strcmp (unit_name, scale.name .c_str ()) == 0 ){
2608
-
2609
- num = scale.ConvertTo ( pure_number, dimensions.GetNative (dimension) );
2610
- // printf("valll %f\n",num);
2611
- return true ;
2612
- }
2613
- }
2614
- // unit name not found in list!
2615
- std::string known_list; for ( auto scale : dimensions.GetUnits (dimension) ) known_list += " " , known_list += scale.name ;
2616
- log.error (eLocation, " unknown %s attribute units: %s for %s (supported:%s)" , attr_name, unit_name, dimensions.Stringify (dimension).c_str (), known_list.c_str () );
2617
- return false ;
2618
2684
2685
+ // find the dimension
2686
+ if ( !dimensions.Has (sDimension ) ){
2687
+ log.error (eTag, " unknown dimension %s" , sDimension );
2688
+ return false ;
2689
+ }
2690
+ dimension = dimensions.Get (sDimension );
2691
+ return true ;
2619
2692
}
2620
2693
2621
-
2622
2694
template <typename UnitType>
2623
2695
bool ParseValueAcrossSegOrSegGroup (const ImportLogger &log, const pugi::xml_node &eAppliedOn, const char *attr_name, const Morphology &morph , ValueAcrossSegOrSegGroup &applied_on){
2624
2696
@@ -5844,6 +5916,74 @@ struct ImportState{
5844
5916
}
5845
5917
net.input_lists .add (list, list_name);
5846
5918
}
5919
+ // extensions!
5920
+ else if (strcmp (eNetEl.name (), " EdenTimeSeriesReader" ) == 0 ){
5921
+ const auto &eRea = eNetEl;
5922
+
5923
+ auto name = RequiredNmlId (log, eRea);
5924
+ if (!name) return false ;
5925
+ if (net.data_readers .has (name)){
5926
+ log.error (eRea, " %s %s already defined" , eRea.name (), name);
5927
+ return false ;
5928
+ }
5929
+
5930
+ Network::TimeSeriesReader reader;
5931
+
5932
+ const char *sHref = RequiredAttribute ( log, eRea, " href" );
5933
+ if (!sHref ) return false ;
5934
+ auto &url = reader.source_url ;
5935
+ url = sHref ; // NB validate on backend, until the url format is standardized LATER (or even better, leave it up to the backend?)
5936
+
5937
+ // XXX if it's a file url, much like the current behaviour of OutputFile, it's relative to the cwd of the program, not the file being parsed! TODO specify a way to selecthow to behave, or at least document -- filename
5938
+ // here's a trick: use "no uri scheme" to mean "relative to xml file" and "file://" to mean "relative to working directory", TODO document it.
5939
+ std::string scheme, auth_path;
5940
+ if (!GetUrlScheme (url, scheme, auth_path)){
5941
+ const char *loading_from_file = log.GetFilenameFromElement (eRea);
5942
+ url = GetRelativeFilePath ((loading_from_file ? loading_from_file : " ." ), url);
5943
+ }
5944
+ // else keep the url as is
5945
+
5946
+ const char *sFormat = RequiredAttribute ( log, eRea, " format" );
5947
+ if (!sFormat ) return false ;
5948
+ reader.data_format = sFormat ; // NB validate on backend, until the url format is standardized LATER (or even better, leave it up to the backend?)
5949
+
5950
+ const char *sInstances = RequiredAttribute ( log, eRea, " instances" );
5951
+ if (!sInstances ) return false ;
5952
+ if (!( StrToL (sInstances , reader.instances ) && reader.instances > 0 )){
5953
+ log.error (eRea, " \" instances\" must be a positive integer, not %s" , sInstances );
5954
+ return false ;
5955
+ }
5956
+
5957
+ for (const auto &eReaEl : eRea.children ()){
5958
+ if ( strcmp (eReaEl.name (), " InputColumn" ) == 0 ){
5959
+
5960
+ auto name = RequiredNmlId (log, eReaEl);
5961
+ if (!name) return false ;
5962
+ if (reader.columns .has (name)){
5963
+ log.error (eReaEl, " %s %s already defined in %s" , eReaEl.name (), name, eRea.name ());
5964
+ return false ;
5965
+ }
5966
+
5967
+ Network::TimeSeriesReader::InputColumn column = {Dimension::Unity (), dimensions.GetNative (Dimension::Unity ())};
5968
+ if (!ParseDimensionAttribute (log, eReaEl, dimensions, column.dimension )) return false ;
5969
+ const char *sUnits = RequiredAttribute ( log, eReaEl, " units" ); if (!sInstances ) return false ;
5970
+ if (!ValidateGetUnits (LogWithElement (log,eReaEl), dimensions, column.dimension , sUnits , column.units )) return false ;
5971
+
5972
+ reader.columns .add (column, name); // yay!
5973
+ }
5974
+ else {
5975
+ // unknown, ignore
5976
+ }
5977
+ }
5978
+
5979
+ // one last consistency check
5980
+ if (reader.columns .contents .empty ()){
5981
+ log.error (eRea, " %s must have one or more <InputColumn>s" , eRea.name ());
5982
+ return false ;
5983
+ }
5984
+
5985
+ net.data_readers .add (reader, name); // yay!
5986
+ }
5847
5987
else {
5848
5988
// unknown, ignore
5849
5989
}
@@ -6640,7 +6780,7 @@ struct ImportState{
6640
6780
std::string filename_s = GetRelativeFilePath ((loading_from_file ? loading_from_file : " ." ), sRelFilename );
6641
6781
const char *filename = filename_s.c_str ();
6642
6782
6643
- std::ifstream fin (filename, std::ios::binary);
6783
+ std::ifstream fin (filename, std::ios::binary); // binary mode, to count CRLF as two bytes
6644
6784
// check if opening a file failed
6645
6785
if (fin.fail ()) {
6646
6786
log.error (eSimEl, " could not open file \" %s\" : %s" , filename, strerror (errno));
@@ -6823,11 +6963,11 @@ struct ImportState{
6823
6963
log.error (lineno, units_token, " there are no specified %s units" , dimensions.Stringify (dimension).c_str () );
6824
6964
return false ;
6825
6965
}
6826
- bool units_ok = false ;
6966
+ bool units_ok = false ; // TODO ValidateGetUnits
6827
6967
for ( auto scale : dimensions.GetUnits (dimension) ){
6828
6968
if (unit_name == scale.name ){
6829
6969
real_value_units = scale;
6830
- value = scale.ConvertTo ( value, dimensions.GetNative (dimension) ); // adjust units right away, if it's a single value. If it's multi, ajust as the values rows are being read.
6970
+ value = scale.ConvertTo ( value, dimensions.GetNative (dimension) ); // adjust units right away, if it's a single value. If it's multi, adjust as the values rows are being read.
6831
6971
units_ok = true ;
6832
6972
break ;
6833
6973
}
@@ -8061,18 +8201,7 @@ struct ImportState{
8061
8201
};
8062
8202
8063
8203
auto ParseBaseNamedProperty = [ &dimensions = dimensions ](const ImportLogger &log, const pugi::xml_node &eProp, ComponentType::BaseNamedProperty &prop_record){
8064
- auto dimension = eProp.attribute (" dimension" ).value ();
8065
- if (!*dimension){
8066
- log.error (eProp, " dimension attribute missing" );
8067
- return false ;
8068
- }
8069
-
8070
- // find the dimension
8071
- if ( !dimensions.Has (dimension) ){
8072
- log.error (eProp, " unknown dimension %s" , dimension);
8073
- return false ;
8074
- }
8075
- prop_record.dimension = dimensions.Get (dimension);
8204
+ if (!ParseDimensionAttribute (log, eProp, dimensions, prop_record.dimension )) return false ;
8076
8205
return true ;
8077
8206
};
8078
8207
0 commit comments