Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update C++ codebase for handling of feature dependencies [vcs: #minor] #334

Merged
merged 94 commits into from
Jan 10, 2024

Conversation

anilbey
Copy link
Contributor

@anilbey anilbey commented Nov 29, 2023

Changes

Following up on #321, focusing on the handling of feature dependencies.

getFeatures Function for Enhanced Dependency Handling

  • Consistent Result Differentiation: The updated getFeatures now consistently distinguishes between empty results and actual failures.

  • Restricted Access for Safety: The introduction of constant (const) access modifiers in the new implementation restricts unnecessary access to feature data, preventing unintended changes.

  • Centralized Exception and Missing Value Handling: Exceptions and missing values are now handled once for all features in a unified manner, replacing the previous approach where each feature managed these issues separately. This consistency streamlines error handling and arguably improves code readability.

Before

int LibV2::AP_rise_time(mapStr2intVec& IntFeatureData,
                        mapStr2doubleVec& DoubleFeatureData,
                        mapStr2Str& StringData) {
  int retval;
  vector<double> t;
  retval = getVec(DoubleFeatureData, StringData, "T", t);
  if (retval < 0) return -1;
  vector<int> apbeginindices;
  retval = getVec(IntFeatureData, StringData, "AP_begin_indices",
                     apbeginindices);
  if (retval < 0) return -1;
  vector<int> peakindices;
  retval = getVec(IntFeatureData, StringData, "peak_indices",
                     peakindices);
  if (retval < 0) return -1;
  vector<double> v;
  retval = getVec(DoubleFeatureData, StringData, "V", v);
  if (retval < 0) return -1;
  vector<double> AP_amplitude;
  retval =
      getVec(DoubleFeatureData, StringData, "AP_amplitude", AP_amplitude);
  if (retval < 0) {
    GErrorStr += "Error calculating AP_amplitude for mean_AP_amplitude";
    return -1;
  } else if (retval == 0) {
    GErrorStr += "No spikes found when calculating mean_AP_amplitude";
    return -1;
  } else if (AP_amplitude.size() == 0) {
    GErrorStr += "No spikes found when calculating mean_AP_amplitude";
    return -1;
  }
  // Get rise begin percentage
  vector<double> risebeginperc;
  retval = getVec(DoubleFeatureData, StringData, "rise_start_perc", risebeginperc);
  if (retval <= 0) {
    risebeginperc.push_back(0.0);
  }
  // Get rise end percentage
  vector<double> riseendperc;
  retval = getVec(DoubleFeatureData, StringData, "rise_end_perc", riseendperc);
  if (retval <= 0) {
    riseendperc.push_back(1.0);
  }
  vector<double> aprisetime;
  retval = __AP_rise_time(t, v, apbeginindices, peakindices, AP_amplitude, risebeginperc[0], riseendperc[0], aprisetime);
  if (retval >= 0) {
    setVec(DoubleFeatureData, StringData, "AP_rise_time", aprisetime);
  }
  return retval;
}

After

int LibV2::AP_rise_time(mapStr2intVec& IntFeatureData,
                        mapStr2doubleVec& DoubleFeatureData,
                        mapStr2Str& StringData) {
  // Fetching all required features in one go.
  const auto& doubleFeatures = getFeatures(DoubleFeatureData, 
                                    {"T", "V", "AP_amplitude", "rise_start_perc", "rise_end_perc"});
  const auto& intFeatures = getFeatures(IntFeatureData, 
                                 {"AP_begin_indices", "peak_indices"});
  vector<double> aprisetime;
  int retval = __AP_rise_time(
      doubleFeatures.at("T"),
      doubleFeatures.at("V"),
      intFeatures.at("AP_begin_indices"),
      intFeatures.at("peak_indices"),
      doubleFeatures.at("AP_amplitude"),
      doubleFeatures.at("rise_start_perc").empty() ? 0.0 : doubleFeatures.at("rise_start_perc").front(),
      doubleFeatures.at("rise_end_perc").empty() ? 1.0 : doubleFeatures.at("rise_end_perc").front(),
      aprisetime
  );
  if (retval > 0) {
    setVec(DoubleFeatureData, StringData, "AP_rise_time", aprisetime);
  }
  return retval;
}

Simplifying Variable Handling

Eliminating Unnecessary Wildcards

  • Context: In our C++ code, wildcards lose their relevance when used with Python, as Python's variables naturally offer the flexibility wildcards are meant to provide.
  • Advantage: Removing these wildcards makes our code leaner and more efficient, as Python itself can accomplish these tasks without needing additional C++ scripting.
  • Impact: This alteration has no negative effect on downstream applications like bluepyefe and simplifies feature representation in cfeature, DependencyTree, and mapoperations.
  • Example: The example case is the use of "location_AIS" as a wildcard in C++. Such explicit use is redundant when we interface with Python.
 int LibV5::AP_phaseslope_AIS(mapStr2intVec& IntFeatureData,
                             mapStr2doubleVec& DoubleFeatureData,
                             mapStr2Str& StringData) {
  int retVal;
  vector<double> ap_phaseslopes;
  retVal = getVec(DoubleFeatureData, StringData,
                        "AP_phaseslope;location_AIS", ap_phaseslopes);
  if (retVal < 0) return -1;
  setVec(DoubleFeatureData, StringData, "AP_phaseslope_AIS",
               ap_phaseslopes);
  return retVal;
}

Merging efel.h/efel.cpp into cppcore

  • Reason: These two files offer similar functionalities but are maintained separately, which is inefficient.
  • Benefit: Merging them reduces the number of files to compile and manage, thereby making our codebase more efficient and easier to navigate.

Embracing Flexibility with Templates

  • Update: Substitute specific functions like getIntParam and getDoubleParam with a unified getParam.
  • Outcome: This change prevents code duplication and allows our system to handle different data types more effectively.

Update:

14.12.2023

Added more clarity on the removal of E* features by quoting @darshanmandge .

" Feature starting with E such as E6, here seem to be duplicates of existing features with wildcard for APWaveform."

The names of those features are as follows: "E6 E7 E39 E39_cod E2 E3 E4 E5 E8 E9 E10 E11 E12 E13 E14 E15 E16 E17 E18 E19 E20 E21 E22 E23 E24 E25 E26 E27 E40"

08.01.2024

  • bpap_attenuation feature is added to the Python API.
  • Spikecount, Spikecount_stimint, burst_number and strict_burst_number features migrated to Python from C++.
  • check_ais_initiation is added to the Python API.

@anilbey
Copy link
Contributor Author

anilbey commented Jan 8, 2024

@darshanmandge, @AurelienJaquier I tried to address all of your reviews. Could you take another look to ensure the modifications align with your expectations?

@anilbey anilbey changed the title Modernising C++ codebase for handling of feature dependencies update C++ codebase for handling of feature dependencies #minor Jan 8, 2024
return spike_count()


def spike_count() -> numpy.ndarray:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to have a new spike_count feature and deprecating Spikecount? Also since it stays as Spikecount in the documentation.
Same question for Spikecount_stimint

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future I thought keeping Python naming convention would be better. I wanted to deprecate it now so that if we change the naming convention in the future we can say: this was deprecated 2 major releases ago.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally all features should have a consistent naming. Most of them are underscore separated already e.g.
adaptation_index or ohmic_input_resistance. Spikecount was an exception. What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, sounds good. But then maybe we should update the documentation also.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see Spikecount has been used in several places in BluePyOpt and BluePyEModel. There can be lot of warnings from old code at multiple places as it is one of the most common features.

Do all the other features follow consistent naming convention? If not, can we keep the name and feature Spikecount ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Thanks!

Copy link
Contributor Author

@anilbey anilbey Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While testing bluepyopt, bluepyefe, bluepyemodel I will make sure this warning does not occur multiple times.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you add spike_count in all_pyfeatures to make it a valid feature? If you want to deprecate Spikecount I mean. Also modify documentation so that feature name is spike_count while keeping a note for Spikecount explaining that it is deprecated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "spike_count" is there already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating the docs

@darshanmandge
Copy link
Contributor

@darshanmandge, @AurelienJaquier I tried to address all of your reviews. Could you take another look to ensure the modifications align with your expectations?

Before merging this PR, can you check if works correctly with BluePyOpt, BluePyEfe and BluePyEModel e.g. the tests and examples? This would be another level of check to ensure eFEL works well with these software. :)

@anilbey
Copy link
Contributor Author

anilbey commented Jan 8, 2024

Downstream workflows to be tested before merging this PR:

  • bluepyefe
  • bluepyopt
  • bluepyemodel

bluepyefe

py3: OK (56.08=setup[49.34]+cmd[6.75] seconds)
congratulations :) (56.42 seconds)

bluepyopt

==================================================================== 180 passed, 12 deselected, 16 warnings in 19.54s =====================================================================
===================================================================== 11 passed, 181 deselected in 390.37s (0:06:30) ======================================================================
====================================================================== 1 passed, 191 deselected in 215.89s (0:03:35) ======================================================================
py3-unit-functional-style: OK (740.07=setup[76.88]+cmd[7.10,1.13,20.28,27.37,390.92,216.40] seconds)

bluepyemodel

========================================================================= 75 passed, 52 warnings in 176.40s (0:02:56) ==========================================================================

@@ -1711,6 +1711,8 @@ def test_getFeatureNames():
test_data_path = testdata_dir.parent / 'featurenames.json'
with open(test_data_path, 'r') as featurenames_json:
expected_featurenames = json.load(featurenames_json)
# add the new names for the deprecated ones
expected_featurenames += ["spike_count", "spike_count_stimint"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The featurenames_json should reflect all the features that we have. If we don't want to duplicate spike_count, it would be better I think to have spike_count in the file, and add deprecated Spikecount in code here to legacy, instead of the inverse as we have now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok makes sense so renaming Spikecount -> spike_count in the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in the last push.

@@ -97,6 +97,8 @@ def test_getFeatureNames(self): # pylint: disable=R0201
test_data_path = os.path.join(testdata_dir, '../featurenames.json')
with open(test_data_path, 'r') as featurenames_json:
expected_featurenames = json.load(featurenames_json)
# add the new names for the deprecated ones
expected_featurenames += ["spike_count", "spike_count_stimint"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as comment above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in the latest push.


number of spikes in the trace, including outside of stimulus interval

- **Required features**: LibV1:peak_indices
- **Units**: constant
- **Pseudocode**: ::

Spikecount = len(peak_indices)
spike_count = len(peak_indices)

**Note**: In the future this feature will be called "spike_count".
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please tell in the note that this feature replaces Spikecount, that Spikecount is deprecated, but can still be used for the moment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I also noticed I forgot to update this one. Done in the last commit. 👍

@anilbey anilbey changed the title update C++ codebase for handling of feature dependencies #minor update C++ codebase for handling of feature dependencies [vcs: #minor] Jan 10, 2024
Copy link
Collaborator

@AurelienJaquier AurelienJaquier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!

@anilbey anilbey merged commit cc9a2b7 into master Jan 10, 2024
8 checks passed
@anilbey anilbey deleted the wildcards branch January 10, 2024 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants