diff --git a/moose-core/CMakeLists.txt b/moose-core/CMakeLists.txt index 9bf8255c1dc2d3f3cafe068ef33daa5b84392b5f..3be4235b3d563ff57adfe0d952ebecc84c44d993 100644 --- a/moose-core/CMakeLists.txt +++ b/moose-core/CMakeLists.txt @@ -130,6 +130,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) if(WITH_BOOST) set(WITH_BOOST_ODE ON) + set(WITH_GSL OFF) endif(WITH_BOOST) # If using BOOST ODE2 library to solve ODE system, then don't use GSL. diff --git a/moose-core/biophysics/Neuron.cpp b/moose-core/biophysics/Neuron.cpp index aa86759175fc376489128abe3f4a14d1caae32b5..3fbab6d0a237cd878e4874e1accd3046cf403c61 100644 --- a/moose-core/biophysics/Neuron.cpp +++ b/moose-core/biophysics/Neuron.cpp @@ -315,11 +315,17 @@ const Cinfo* Neuron::initCinfo() "The expression for the *spacing* field must evaluate to > 0 for " "the spine to be installed. For example, if the expresssion is\n" " H(1 - L) \n" - "then the systemwill only put spines closer than " + "then the system will only put spines closer than " "one length constant from the soma, and zero elsewhere. \n" "Available spine parameters are: \n" "spacing, minSpacing, size, sizeDistrib " - "angle, angleDistrib \n", + "angle, angleDistrib \n" + "minSpacing sets the granularity of sampling (typically about 0.1*" + "spacing) for the usual case where spines are spaced randomly. " + "If minSpacing < 0 then the spines are spaced equally at " + "'spacing', unless the dendritic segment length is smaller than " + "'spacing'. In that case it falls back to the regular random " + "placement method.", &Neuron::setSpineDistribution, &Neuron::getSpineDistribution ); @@ -1788,6 +1794,19 @@ static void addPos( unsigned int segIndex, unsigned int eIndex, vector< unsigned int >& elistIndex, vector< double >& pos ) { + if ( minSpacing < 0.0 ) { + // Use uniform spacing + for ( double position = spacing * 0.5; + position < dendLength; position += spacing ) { + seglistIndex.push_back( segIndex ); + elistIndex.push_back( eIndex ); + pos.push_back( position ); + } + if ( dendLength > spacing * 0.5 ) + return; + // If the dend length is too small for regular placement, + // fall back to using probability to decide if segment gets spine + } if ( minSpacing < spacing * 0.1 && minSpacing < 1e-7 ) minSpacing = spacing * 0.1; if ( minSpacing > spacing * 0.5 ) @@ -1860,13 +1879,6 @@ void Neuron::makeSpacingDistrib( const vector< ObjId >& elist, { double spacing = val[ j + nuParser::EXPR ]; double spacingDistrib = parser.eval( val.begin() + j ); - if ( spacingDistrib > spacing || spacingDistrib < 0 ) - { - cout << "Warning: Neuron::makeSpacingDistrib: " << - "0 < " << spacingDistrib << " < " << spacing << - " fails on " << elist[i].path() << ". Using 0.\n"; - spacingDistrib = 0.0; - } map< Id, unsigned int>::const_iterator lookupDend = segIndex_.find( elist[i] ); if ( lookupDend != segIndex_.end() ) diff --git a/moose-core/ksolve/Ksolve.cpp b/moose-core/ksolve/Ksolve.cpp index 2277e5ffb37d5041eb56487cfa09766568b9e980..81bc118fc29118f5a746740710879a286468ffea 100644 --- a/moose-core/ksolve/Ksolve.cpp +++ b/moose-core/ksolve/Ksolve.cpp @@ -235,7 +235,7 @@ static const Cinfo* ksolveCinfo = Ksolve::initCinfo(); Ksolve::Ksolve() : -#if USE_GSL +#ifdef USE_GSL method_( "rk5" ), #elif USE_BOOST_ODE method_( "rk5a" ), @@ -248,19 +248,12 @@ Ksolve::Ksolve() dsolve_(), dsolvePtr_( 0 ) { + ; } Ksolve::~Ksolve() { -#if 0 - char* p = getenv( "MOOSE_SHOW_SOLVER_PERF" ); - if( p != NULL ) - { - cout << "Info: Ksolve (+Dsolve) took " << totalTime_ << " seconds and took " << numSteps_ - << " steps." << endl; - - } -#endif + ; } ////////////////////////////////////////////////////////////// @@ -378,6 +371,7 @@ void Ksolve::setStoich( Id stoich ) assert( stoich.element()->cinfo()->isA( "Stoich" ) ); stoich_ = stoich; stoichPtr_ = reinterpret_cast< Stoich* >( stoich.eref().data() ); + if ( !isBuilt_ ) { OdeSystem ode; @@ -386,6 +380,7 @@ void Ksolve::setStoich( Id stoich ) // ode.initStepSize = getEstimatedDt(); ode.initStepSize = 0.01; // This will be overridden at reinit. ode.method = method_; + #ifdef USE_GSL ode.gslSys.dimension = stoichPtr_->getNumAllPools(); if ( ode.gslSys.dimension == 0 ) @@ -416,6 +411,7 @@ void Ksolve::setStoich( Id stoich ) #endif isBuilt_ = true; } + } Id Ksolve::getDsolve() const diff --git a/moose-core/ksolve/OdeSystem.h b/moose-core/ksolve/OdeSystem.h index 6a3ede6dcf26d589ada9b4ffd0dfa15a16cfb557..511513b57cccfe8cdb7e2911e58e769bdb9b4eab 100644 --- a/moose-core/ksolve/OdeSystem.h +++ b/moose-core/ksolve/OdeSystem.h @@ -26,15 +26,16 @@ class OdeSystem { {;} std::string method; - // GSL stuff + + double initStepSize; + double epsAbs; // Absolute error + double epsRel; // Relative error #ifdef USE_GSL + // GSL stuff gsl_odeiv2_system gslSys; const gsl_odeiv2_step_type* gslStep; #endif - double initStepSize; - double epsAbs; // Absolute error - double epsRel; // Relative error #if USE_BOOST_ODE size_t dimension; diff --git a/moose-core/ksolve/SteadyStateBoost.cpp b/moose-core/ksolve/SteadyStateBoost.cpp index a96439baec7d42630d9fe2441641a2c5c5521bd0..5d31165d66d52edc2d493aab558b429f151aff1b 100644 --- a/moose-core/ksolve/SteadyStateBoost.cpp +++ b/moose-core/ksolve/SteadyStateBoost.cpp @@ -85,27 +85,6 @@ struct reac_info const Cinfo* SteadyState::initCinfo() { - /** - * This picks up the entire Stoich data structure - static Finfo* gslShared[] = - { - new SrcFinfo( "reinitSrc", Ftype0() ), - new DestFinfo( "assignStoich", - Ftype1< void* >(), - RFCAST( &SteadyState::assignStoichFunc ) - ), - new DestFinfo( "setMolN", - Ftype2< double, unsigned int >(), - RFCAST( &SteadyState::setMolN ) - ), - new SrcFinfo( "requestYsrc", Ftype0() ), - new DestFinfo( "assignY", - Ftype1< double* >(), - RFCAST( &SteadyState::assignY ) - ), - }; - */ - /** * These are the fields of the SteadyState class */ @@ -207,59 +186,59 @@ const Cinfo* SteadyState::initCinfo() // MsgDest definitions /////////////////////////////////////////////////////// static DestFinfo setupMatrix( "setupMatrix", - "This function initializes and rebuilds the matrices used " - "in the calculation.", - new OpFunc0< SteadyState >(&SteadyState::setupMatrix) - ); + "This function initializes and rebuilds the matrices used " + "in the calculation.", + new OpFunc0< SteadyState >(&SteadyState::setupMatrix) + ); static DestFinfo settle( "settle", - "Finds the nearest steady state to the current initial " - "conditions. This function rebuilds the entire calculation " - "only if the object has not yet been initialized.", - new OpFunc0< SteadyState >( &SteadyState::settleFunc ) - ); + "Finds the nearest steady state to the current initial " + "conditions. This function rebuilds the entire calculation " + "only if the object has not yet been initialized.", + new OpFunc0< SteadyState >( &SteadyState::settleFunc ) + ); static DestFinfo resettle( "resettle", - "Finds the nearest steady state to the current initial " - "conditions. This function rebuilds the entire calculation ", - new OpFunc0< SteadyState >( &SteadyState::resettleFunc ) - ); + "Finds the nearest steady state to the current initial " + "conditions. This function rebuilds the entire calculation ", + new OpFunc0< SteadyState >( &SteadyState::resettleFunc ) + ); static DestFinfo showMatrices( "showMatrices", - "Utility function to show the matrices derived for the calculations on the reaction system. Shows the Nr, gamma, and total matrices", - new OpFunc0< SteadyState >( &SteadyState::showMatrices ) - ); + "Utility function to show the matrices derived for the calculations on the reaction system. Shows the Nr, gamma, and total matrices", + new OpFunc0< SteadyState >( &SteadyState::showMatrices ) + ); static DestFinfo randomInit( "randomInit", - "Generate random initial conditions consistent with the mass" - "conservation rules. Typically invoked in order to scan" - "states", - new EpFunc0< SteadyState >( - &SteadyState::randomizeInitialCondition ) - ); + "Generate random initial conditions consistent with the mass" + "conservation rules. Typically invoked in order to scan" + "states", + new EpFunc0< SteadyState >( + &SteadyState::randomizeInitialCondition ) + ); /////////////////////////////////////////////////////// // Shared definitions /////////////////////////////////////////////////////// static Finfo * steadyStateFinfos[] = { - &stoich, // Value - &badStoichiometry, // ReadOnlyValue - &isInitialized, // ReadOnlyValue - &nIter, // ReadOnlyValue - &status, // ReadOnlyValue - &maxIter, // Value - &convergenceCriterion, // ReadOnlyValue - &numVarPools, // ReadOnlyValue - &rank, // ReadOnlyValue - &stateType, // ReadOnlyValue - &nNegEigenvalues, // ReadOnlyValue - &nPosEigenvalues, // ReadOnlyValue - &solutionStatus, // ReadOnlyValue - &total, // LookupValue - &eigenvalues, // ReadOnlyLookupValue - &setupMatrix, // DestFinfo - &settle, // DestFinfo - &resettle, // DestFinfo - &showMatrices, // DestFinfo - &randomInit, // DestFinfo + &stoich, // Value + &badStoichiometry, // ReadOnlyValue + &isInitialized, // ReadOnlyValue + &nIter, // ReadOnlyValue + &status, // ReadOnlyValue + &maxIter, // Value + &convergenceCriterion, // ReadOnlyValue + &numVarPools, // ReadOnlyValue + &rank, // ReadOnlyValue + &stateType, // ReadOnlyValue + &nNegEigenvalues, // ReadOnlyValue + &nPosEigenvalues, // ReadOnlyValue + &solutionStatus, // ReadOnlyValue + &total, // LookupValue + &eigenvalues, // ReadOnlyLookupValue + &setupMatrix, // DestFinfo + &settle, // DestFinfo + &resettle, // DestFinfo + &showMatrices, // DestFinfo + &randomInit, // DestFinfo }; @@ -278,7 +257,7 @@ const Cinfo* SteadyState::initCinfo() "Note that the method finds unstable as well as stable fixed " "points.\n " "The SteadyState class also provides a utility function " - "*randomInit()* to " + "*randomInit()* to " "randomly initialize the concentrations, within the constraints " "of stoichiometry. This is useful if you are trying to find " "the major fixed points of the system. Note that this is " @@ -368,12 +347,10 @@ void SteadyState::setStoich( Id value ) numVarPools_ = Field< unsigned int >::get( stoich_, "numVarPools" ); nReacs_ = Field< unsigned int >::get( stoich_, "numRates" ); setupSSmatrix(); - double vol = LookupField< unsigned int, double >::get( - stoichPtr->getCompartment(), "oneVoxelVolume", 0 ); + double vol = LookupField< unsigned int, double >::get(stoichPtr->getCompartment(), "oneVoxelVolume", 0 ); pool_.setVolume( vol ); pool_.setStoich( stoichPtr, 0 ); - pool_.updateAllRateTerms( stoichPtr->getRateTerms(), - stoichPtr->getNumCoreRates() ); + pool_.updateAllRateTerms( stoichPtr->getRateTerms(), stoichPtr->getNumCoreRates() ); isInitialized_ = 1; } @@ -513,9 +490,9 @@ void SteadyState::showMatrices() return; } int numConsv = numVarPools_ - rank_; - cout << "Totals: "; + cout << "Totals: "; for ( int i = 0; i < numConsv; ++i ) - cout << total_[i] << " "; + cout << total_[i] << " "; cout << endl; cout << "gamma " << gamma_ << endl; cout << "Nr " << Nr_ << endl; @@ -549,9 +526,7 @@ void SteadyState::setupSSmatrix() { double x = 0; if ( j == colIndex[k] && k < rowStart[i+1] ) - { x = entry[k++]; - } N(i,j) = x; LU_(i,j) = x; } @@ -714,7 +689,7 @@ static bool isSolutionValid( const vector< double >& x ) for( size_t i = 0; i < x.size(); i++ ) { double v = x[i]; - if ( std::isnan( v ) or std::isinf( v ) ) + if ( std::isnan( v ) || std::isinf( v ) ) { cout << "Warning: SteadyState iteration gave nan/inf concs\n"; return false; diff --git a/moose-core/ksolve/SteadyStateGsl.cpp b/moose-core/ksolve/SteadyStateGsl.cpp index 295b5256d5edcd4e574ef7b9e99222db4aef9e23..5aa0941dacf666a66978c5b1ac66872dfe8731f9 100644 --- a/moose-core/ksolve/SteadyStateGsl.cpp +++ b/moose-core/ksolve/SteadyStateGsl.cpp @@ -87,20 +87,20 @@ const Cinfo* SteadyState::initCinfo() * This picks up the entire Stoich data structure static Finfo* gslShared[] = { - new SrcFinfo( "reinitSrc", Ftype0::global() ), - new DestFinfo( "assignStoich", - Ftype1< void* >::global(), - RFCAST( &SteadyState::assignStoichFunc ) - ), - new DestFinfo( "setMolN", - Ftype2< double, unsigned int >::global(), - RFCAST( &SteadyState::setMolN ) - ), - new SrcFinfo( "requestYsrc", Ftype0::global() ), - new DestFinfo( "assignY", - Ftype1< double* >::global(), - RFCAST( &SteadyState::assignY ) - ), + new SrcFinfo( "reinitSrc", Ftype0::global() ), + new DestFinfo( "assignStoich", + Ftype1< void* >::global(), + RFCAST( &SteadyState::assignStoichFunc ) + ), + new DestFinfo( "setMolN", + Ftype2< double, unsigned int >::global(), + RFCAST( &SteadyState::setMolN ) + ), + new SrcFinfo( "requestYsrc", Ftype0::global() ), + new DestFinfo( "assignY", + Ftype1< double* >::global(), + RFCAST( &SteadyState::assignY ) + ), }; */ @@ -205,61 +205,61 @@ const Cinfo* SteadyState::initCinfo() // MsgDest definitions /////////////////////////////////////////////////////// static DestFinfo setupMatrix( "setupMatrix", - "This function initializes and rebuilds the matrices used " - "in the calculation.", - new OpFunc0< SteadyState >(&SteadyState::setupMatrix) - ); + "This function initializes and rebuilds the matrices used " + "in the calculation.", + new OpFunc0< SteadyState >(&SteadyState::setupMatrix) + ); static DestFinfo settle( "settle", - "Finds the nearest steady state to the current initial " - "conditions. This function rebuilds the entire calculation " - "only if the object has not yet been initialized.", - new OpFunc0< SteadyState >( &SteadyState::settleFunc ) - ); + "Finds the nearest steady state to the current initial " + "conditions. This function rebuilds the entire calculation " + "only if the object has not yet been initialized.", + new OpFunc0< SteadyState >( &SteadyState::settleFunc ) + ); static DestFinfo resettle( "resettle", - "Finds the nearest steady state to the current initial " - "conditions. This function rebuilds the entire calculation ", - new OpFunc0< SteadyState >( &SteadyState::resettleFunc ) - ); + "Finds the nearest steady state to the current initial " + "conditions. This function rebuilds the entire calculation ", + new OpFunc0< SteadyState >( &SteadyState::resettleFunc ) + ); static DestFinfo showMatrices( "showMatrices", - "Utility function to show the matrices derived for the calculations on the reaction system. Shows the Nr, gamma, and total matrices", - new OpFunc0< SteadyState >( &SteadyState::showMatrices ) - ); + "Utility function to show the matrices derived for the " + "calculations on the reaction system. Shows the Nr, gamma, and total matrices", + new OpFunc0< SteadyState >( &SteadyState::showMatrices ) + ); static DestFinfo randomInit( "randomInit", - "Generate random initial conditions consistent with the mass" - "conservation rules. Typically invoked in order to scan" - "states", - new EpFunc0< SteadyState >( - &SteadyState::randomizeInitialCondition ) - ); + "Generate random initial conditions consistent with the mass" + "conservation rules. Typically invoked in order to scan" + "states", + new EpFunc0< SteadyState >( + &SteadyState::randomizeInitialCondition ) + ); + /////////////////////////////////////////////////////// // Shared definitions /////////////////////////////////////////////////////// static Finfo * steadyStateFinfos[] = { - &stoich, // Value - &badStoichiometry, // ReadOnlyValue - &isInitialized, // ReadOnlyValue - &nIter, // ReadOnlyValue - &status, // ReadOnlyValue - &maxIter, // Value - &convergenceCriterion, // ReadOnlyValue - &numVarPools, // ReadOnlyValue - &rank, // ReadOnlyValue - &stateType, // ReadOnlyValue - &nNegEigenvalues, // ReadOnlyValue - &nPosEigenvalues, // ReadOnlyValue - &solutionStatus, // ReadOnlyValue - &total, // LookupValue - &eigenvalues, // ReadOnlyLookupValue - &setupMatrix, // DestFinfo - &settle, // DestFinfo - &resettle, // DestFinfo - &showMatrices, // DestFinfo - &randomInit, // DestFinfo - - + &stoich, // Value + &badStoichiometry, // ReadOnlyValue + &isInitialized, // ReadOnlyValue + &nIter, // ReadOnlyValue + &status, // ReadOnlyValue + &maxIter, // Value + &convergenceCriterion, // ReadOnlyValue + &numVarPools, // ReadOnlyValue + &rank, // ReadOnlyValue + &stateType, // ReadOnlyValue + &nNegEigenvalues, // ReadOnlyValue + &nPosEigenvalues, // ReadOnlyValue + &solutionStatus, // ReadOnlyValue + &total, // LookupValue + &eigenvalues, // ReadOnlyLookupValue + &setupMatrix, // DestFinfo + &settle, // DestFinfo + &resettle, // DestFinfo + &showMatrices, // DestFinfo + &randomInit, // DestFinfo }; static string doc[] = @@ -276,7 +276,7 @@ const Cinfo* SteadyState::initCinfo() "Note that the method finds unstable as well as stable fixed " "points.\n " "The SteadyState class also provides a utility function " - "*randomInit()* to " + "*randomInit()* to " "randomly initialize the concentrations, within the constraints " "of stoichiometry. This is useful if you are trying to find " "the major fixed points of the system. Note that this is " @@ -316,7 +316,6 @@ static const Cinfo* steadyStateCinfo = SteadyState::initCinfo(); /////////////////////////////////////////////////// // Class function definitions /////////////////////////////////////////////////// - SteadyState::SteadyState() : nIter_( 0 ), @@ -382,9 +381,10 @@ void SteadyState::setStoich( Id value ) double vol = LookupField< unsigned int, double >::get( stoichPtr->getCompartment(), "oneVoxelVolume", 0 ); pool_.setVolume( vol ); - pool_.setStoich( stoichPtr, 0 ); - pool_.updateAllRateTerms( stoichPtr->getRateTerms(), - stoichPtr->getNumCoreRates() ); + + pool_.setStoich( stoichPtr, nullptr ); + + pool_.updateAllRateTerms( stoichPtr->getRateTerms(), stoichPtr->getNumCoreRates() ); isInitialized_ = 1; } @@ -548,9 +548,9 @@ void SteadyState::showMatrices() return; } int numConsv = numVarPools_ - rank_; - cout << "Totals: "; + cout << "Totals: "; for ( int i = 0; i < numConsv; ++i ) - cout << total_[i] << " "; + cout << total_[i] << " "; cout << endl; #ifdef USE_GSL print_gsl_mat( gamma_, "gamma" ); @@ -584,7 +584,7 @@ void SteadyState::setupSSmatrix() { gsl_matrix_set (LU_, i, i + nReacs_, 1 ); unsigned int k = rowStart[i]; - // cout << endl << i << ": "; + // cout << endl << i << ": "; for ( unsigned int j = 0; j < nReacs_; ++j ) { double x = 0; @@ -592,7 +592,7 @@ void SteadyState::setupSSmatrix() { x = entry[k++]; } - // cout << " " << x; + // cout << " " << x; gsl_matrix_set (N, i, j, x); gsl_matrix_set (LU_, i, j, x ); } @@ -637,10 +637,10 @@ void SteadyState::setupSSmatrix() /* cout << "S = ("; for ( unsigned int j = 0; j < numVarPools_; ++j ) - cout << s_->S()[ j ] << ", "; + cout << s_->S()[ j ] << ", "; cout << "), Sinit = ( "; for ( unsigned int j = 0; j < numVarPools_; ++j ) - cout << s_->Sinit()[ j ] << ", "; + cout << s_->Sinit()[ j ] << ", "; cout << ")\n"; */ Id ksolve = Field< Id >::get( stoich_, "ksolve" ); diff --git a/moose-core/ksolve/VoxelPools.cpp b/moose-core/ksolve/VoxelPools.cpp index ea575304362e063afda23ec222e09c227a93019f..25e8ac7e875aed68b074c59d4b8a64dcb88a0593 100644 --- a/moose-core/ksolve/VoxelPools.cpp +++ b/moose-core/ksolve/VoxelPools.cpp @@ -6,7 +6,8 @@ ** GNU Lesser General Public License version 2.1 ** See the file COPYING.LIB for the full notice. **********************************************************************/ -#include "header.h" + +#include "../basecode/header.h" #ifdef USE_GSL #include <gsl/gsl_errno.h> @@ -66,9 +67,12 @@ void VoxelPools::reinit( double dt ) void VoxelPools::setStoich( Stoich* s, const OdeSystem* ode ) { stoichPtr_ = s; - absTol_ = ode->epsAbs; - relTol_ = ode->epsRel; - method_ = ode->method; + if( ode ) + { + epsAbs_ = ode->epsAbs; + epsRel_ = ode->epsRel; + method_ = ode->method; + } #ifdef USE_GSL if ( ode ) @@ -77,10 +81,9 @@ void VoxelPools::setStoich( Stoich* s, const OdeSystem* ode ) if ( driver_ ) gsl_odeiv2_driver_free( driver_ ); - driver_ = gsl_odeiv2_driver_alloc_y_new( - &sys_, ode->gslStep, ode->initStepSize, - ode->epsAbs, ode->epsRel - ); + driver_ = gsl_odeiv2_driver_alloc_y_new( &sys_, ode->gslStep + , ode->initStepSize, ode->epsAbs, ode->epsRel + ); } #endif VoxelPoolsBase::reinit(); @@ -89,6 +92,7 @@ void VoxelPools::setStoich( Stoich* s, const OdeSystem* ode ) void VoxelPools::advance( const ProcInfo* p ) { double t = p->currTime - p->dt; + #ifdef USE_GSL int status = gsl_odeiv2_driver_apply( driver_, &t, p->currTime, varS()); if ( status != GSL_SUCCESS ) @@ -135,114 +139,41 @@ void VoxelPools::advance( const ProcInfo* p ) * user should provide the stepping size when using fixed dt. This feature * can be incredibly useful on large system. */ - const double fixedDt = 0.1; - if( method_ == "rk2" ) + // Variout stepper times are listed here: + // https://www.boost.org/doc/libs/1_68_0/libs/numeric/odeint/doc/html/boost_numeric_odeint/odeint_in_detail/steppers.html#boost_numeric_odeint.odeint_in_detail.steppers.explicit_steppers + + auto sys = [this](const vector_type_& dy, vector_type_& dydt, const double t) { + VoxelPools::evalRates(this, dy, dydt); }; + + // This is usually the default method for boost: Runge Kutta Fehlberg + if( method_ == "rk5") + odeint::integrate_const( rk_karp_stepper_type_() + , sys , Svec() , p->currTime - p->dt, p->currTime, p->dt); + else if( method_ == "rk2" ) odeint::integrate_const( rk_midpoint_stepper_type_() - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt, p->currTime, std::min( p->dt, fixedDt ) - ); + , sys, Svec(), p->currTime - p->dt, p->currTime, p->dt); else if( method_ == "rk4" ) odeint::integrate_const( rk4_stepper_type_() - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt, p->currTime, std::min( p->dt, fixedDt ) - ); - else if( method_ == "rk5") - odeint::integrate_const( rk_karp_stepper_type_() - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt, p->currTime, std::min( p->dt, fixedDt ) - ); + , sys, Svec(), p->currTime - p->dt, p->currTime, p->dt ); else if( method_ == "rk5a") - odeint::integrate_adaptive( - odeint::make_controlled<rk_karp_stepper_type_>( absTol_, relTol_ ) - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt - , p->currTime - , p->dt - ); + odeint::integrate_adaptive( odeint::make_controlled<rk_dopri_stepper_type_>( epsAbs_, epsRel_ ) + , sys , Svec() , p->currTime - p->dt , p->currTime, p->dt ); else if ("rk54" == method_ ) odeint::integrate_const( rk_karp_stepper_type_() - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt, p->currTime, std::min( p->dt, fixedDt ) - ); + , sys , Svec() , p->currTime - p->dt, p->currTime, p->dt); else if ("rk54a" == method_ ) - odeint::integrate_adaptive( - odeint::make_controlled<rk_karp_stepper_type_>( absTol_, relTol_ ) - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt - , p->currTime - , p->dt - ); - else if ("rk5" == method_ ) - odeint::integrate_const( rk_dopri_stepper_type_() - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt - , p->currTime - , std::min( p->dt, fixedDt ) - ); - else if ("rk5a" == method_ ) - odeint::integrate_adaptive( - odeint::make_controlled<rk_dopri_stepper_type_>( absTol_, relTol_ ) - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt - , p->currTime - , p->dt - ); + odeint::integrate_adaptive( odeint::make_controlled<rk_karp_stepper_type_>( epsAbs_, epsRel_ ) + , sys, Svec(), p->currTime - p->dt, p->currTime, p->dt); else if( method_ == "rk8" ) odeint::integrate_const( rk_felhberg_stepper_type_() - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt, p->currTime, std::min( p->dt, fixedDt ) - ); + , sys, Svec(), p->currTime - p->dt, p->currTime, p->dt); else if( method_ == "rk8a" ) - odeint::integrate_adaptive( - odeint::make_controlled<rk_felhberg_stepper_type_>( absTol_, relTol_ ) - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt - , p->currTime - , p->dt - ); - + odeint::integrate_adaptive( odeint::make_controlled<rk_felhberg_stepper_type_>( epsAbs_, epsRel_ ) + , sys, Svec(), p->currTime - p->dt, p->currTime, p->dt); else - odeint::integrate_adaptive( - odeint::make_controlled<rk_karp_stepper_type_>( absTol_, relTol_ ) - , [this](const vector_type_& dy, vector_type_& dydt, const double t) { - VoxelPools::evalRates(this, dy, dydt ); - } - , Svec() - , p->currTime - p->dt - , p->currTime - , p->dt - ); + odeint::integrate_adaptive( odeint::make_controlled<rk_karp_stepper_type_>( epsAbs_, epsRel_ ) + , sys, Svec(), p->currTime - p->dt, p->currTime, p->dt); #endif if ( !stoichPtr_->getAllowNegative() ) // clean out negatives @@ -353,8 +284,7 @@ void VoxelPools::updateRates( const double* s, double* yprime ) const stoichPtr_->getNumProxyPools(); // totVar should include proxyPools if this voxel does not use them unsigned int totInvar = stoichPtr_->getNumBufPools(); - assert( N.nColumns() == 0 || - N.nRows() == stoichPtr_->getNumAllPools() ); + assert( N.nColumns() == 0 || N.nRows() == stoichPtr_->getNumAllPools() ); assert( N.nColumns() == rates_.size() ); for ( vector< RateTerm* >::const_iterator diff --git a/moose-core/ksolve/VoxelPools.h b/moose-core/ksolve/VoxelPools.h index a39b151c4ed8185c29829c980a38bc5782169da9..04c5bb8c7ed03f96dc1743e8ac5f7bd299448f89 100644 --- a/moose-core/ksolve/VoxelPools.h +++ b/moose-core/ksolve/VoxelPools.h @@ -99,8 +99,8 @@ private: gsl_odeiv2_system sys_; #endif - double absTol_; - double relTol_; + double epsAbs_; + double epsRel_; string method_; }; diff --git a/moose-core/python/rdesigneur/rdesigneur.py b/moose-core/python/rdesigneur/rdesigneur.py index 568bac944455073b43e71ad2cf2dbaa3faf69e9c..447faa110d3b6f9469e9f1719f33bd785ab8d33a 100644 --- a/moose-core/python/rdesigneur/rdesigneur.py +++ b/moose-core/python/rdesigneur/rdesigneur.py @@ -73,6 +73,7 @@ class rdesigneur: combineSegments = True, stealCellFromLibrary = False, verbose = True, + benchmark = False, addSomaChemCompt = False, # Put a soma chemCompt on neuroMesh addEndoChemCompt = False, # Put an endo compartment, typically for ER, on each of the NeuroMesh compartments. diffusionLength= 2e-6, @@ -96,8 +97,8 @@ class rdesigneur: chemDistrib = [], adaptorList= [], stimList = [], - plotList = [], - moogList = [], + plotList = [], # elecpath, geom_expr, object, field, title ['wave' [min max]] + moogList = [], params = None ): """ Constructor of the rdesigner. This just sets up internal fields @@ -110,6 +111,7 @@ class rdesigneur: self.combineSegments = combineSegments self.stealCellFromLibrary = stealCellFromLibrary self.verbose = verbose + self.benchmark = benchmark self.addSomaChemCompt = addSomaChemCompt self.addEndoChemCompt = addEndoChemCompt self.diffusionLength= diffusionLength @@ -138,11 +140,16 @@ class rdesigneur: self.params = params self.adaptorList = adaptorList - self.stimList = stimList - self.plotList = plotList - self.saveList = plotList #ADDED BY Sarthak + try: + self.stimList = [ rstim.convertArg(i) for i in stimList ] + self.plotList = [ rplot.convertArg(i) for i in plotList ] + self.moogList = [ rmoog.convertArg(i) for i in moogList ] + except BuildError as msg: + print("Error: rdesigneur: " + msg) + quit() + + #self.saveList = plotList #ADDED BY Sarthak self.saveAs = [] - self.moogList = moogList self.plotNames = [] self.wavePlotNames = [] self.saveNames = [] @@ -166,7 +173,9 @@ class rdesigneur: ################################################################ def _printModelStats( self ): - print("\n\tRdesigneur: Elec model has", + if not self.verbose: + return + print("\nRdesigneur: Elec model has", self.elecid.numCompartments, "compartments and", self.elecid.numSpines, "spines on", len( self.cellPortionElist ), "compartments.") @@ -184,17 +193,15 @@ class rdesigneur: return self.model = moose.Neutral( modelPath ) self.modelPath = modelPath - print( "[INFO ] rdesigneur: Building model. " ) funcs = [ self.installCellFromProtos, self.buildPassiveDistrib , self.buildChanDistrib, self.buildSpineDistrib, self.buildChemDistrib , self._configureSolvers, self.buildAdaptors, self._buildStims , self._buildPlots, self._buildMoogli, self._configureHSolve - , self._configureClocks, self._printModelStats, self._savePlots + , self._configureClocks, self._printModelStats ] for i, _func in enumerate( funcs ): - if self.verbose: + if self.benchmark: print( " + (%d/%d) executing %25s"%(i, len(funcs), _func.__name__), end=' ' ) - sys.stdout.flush() t0 = time.time() try: _func( ) @@ -203,11 +210,12 @@ class rdesigneur: moose.delete( self.model ) return t = time.time() - t0 - if self.verbose: + if self.benchmark: msg = r' ... DONE' if t > 1: msg += ' %.3f sec' % t print( msg ) + sys.stdout.flush() def installCellFromProtos( self ): if self.stealCellFromLibrary: @@ -463,10 +471,10 @@ class rdesigneur: # Here we hack geomExpr to use it for the syn weight. We assume it # is just a number. In due course # it should be possible to actually evaluate it according to geom. - synWeight = float( stimInfo[1] ) + synWeight = float( stimInfo.geom_expr ) stimObj = [] for i in dendCompts + spineCompts: - path = i.path + '/' + stimInfo[2] + '/sh/synapse[0]' + path = i.path + '/' + stimInfo.relpath + '/sh/synapse[0]' if moose.exists( path ): synInput = make_synInput( name='synInput', parent=path ) synInput.doPeriodic = doPeriodic @@ -486,6 +494,10 @@ class rdesigneur: # Expression can use p, g, L, len, dia, maxP, maxG, maxL. temp = [] for i in self.passiveDistrib: + if (len( i ) < 3) or (len(i) %2 != 1): + raise BuildError( "buildPassiveDistrib: Need 3 + N*2 arguments, have {}".format( len(i) ) ) + + temp.append( '.' ) temp.extend( i ) temp.extend( [""] ) self.elecid.passiveDistribution = temp @@ -608,18 +620,17 @@ class rdesigneur: # [ region_wildcard, region_expr, path, field, title] def _parseComptField( self, comptList, plotSpec, knownFields ): # Put in stuff to go through fields if the target is a chem object - field = plotSpec[3] + field = plotSpec.field if not field in knownFields: print("Warning: Rdesigneur::_parseComptField: Unknown field '{}'".format( field ) ) return (), "" kf = knownFields[field] # Find the field to decide type. if kf[0] in ['CaConcBase', 'ChanBase', 'NMDAChan', 'VClamp']: - objList = self._collapseElistToPathAndClass( comptList, plotSpec[2], kf[0] ) + objList = self._collapseElistToPathAndClass( comptList, plotSpec.relpath, kf[0] ) return objList, kf[1] - elif field in [ 'n', 'conc', 'volume']: - path = plotSpec[2] + path = plotSpec.relpath pos = path.find( '/' ) if pos == -1: # Assume it is in the dend compartment. path = 'dend/' + path @@ -640,13 +651,13 @@ class rdesigneur: voxelVec = [i for i in range(len( em ) ) if em[i] in comptSet ] # Here we collapse the voxelVec into objects to plot. - allObj = moose.vec( self.modelPath + '/chem/' + plotSpec[2] ) + allObj = moose.vec( self.modelPath + '/chem/' + plotSpec.relpath ) #print "####### allObj=", self.modelPath + '/chem/' + plotSpec[2] if len( allObj ) >= len( voxelVec ): objList = [ allObj[int(j)] for j in voxelVec] else: objList = [] - print( "Warn: Rdesigneur::_parseComptField: unknown Object: '%s'" % plotSpec[2] ) + print( "Warning: Rdesigneur::_parseComptField: unknown Object: '", plotSpec.relpath, "'" ) #print "############", chemCompt, len(objList), kf[1] return objList, kf[1] @@ -678,7 +689,7 @@ class rdesigneur: dummy = moose.element( '/' ) k = 0 for i in self.plotList: - pair = i[0] + " " + i[1] + pair = i.elecpath + ' ' + i.geom_expr dendCompts = self.elecid.compartmentsFromExpression[ pair ] spineCompts = self.elecid.spinesFromExpression[ pair ] plotObj, plotField = self._parseComptField( dendCompts, i, knownFields ) @@ -686,26 +697,26 @@ class rdesigneur: assert( plotField == plotField2 ) plotObj3 = plotObj + plotObj2 numPlots = sum( q != dummy for q in plotObj3 ) - if numPlots == 0: - return - - tabname = graphs.path + '/plot' + str(k) - scale = knownFields[i[3]][2] - units = knownFields[i[3]][3] - ymin = i[6] if len(i) > 7 else 0 - ymax = i[7] if len(i) > 7 else 0 - if len( i ) > 5 and i[5] == 'wave': - self.wavePlotNames.append( [ tabname, i[4], k, scale, units, i[3], ymin, ymax ] ) - else: - self.plotNames.append( [ tabname, i[4], k, scale, units, i[3], ymin, ymax ] ) - k += 1 - if i[3] in [ 'n', 'conc', 'volume', 'Gbar' ]: - tabs = moose.Table2( tabname, numPlots ) - else: - tabs = moose.Table( tabname, numPlots ) - if i[3] == 'spikeTime': - tabs.vec.threshold = -0.02 # Threshold for classifying Vm as a spike. - tabs.vec.useSpikeMode = True # spike detect mode on + #print( "PlotList: {0}: numobj={1}, field ={2}, nd={3}, ns={4}".format( pair, numPlots, plotField, len( dendCompts ), len( spineCompts ) ) ) + if numPlots > 0: + tabname = graphs.path + '/plot' + str(k) + scale = knownFields[i.field][2] + units = knownFields[i.field][3] + if i.mode == 'wave': + self.wavePlotNames.append( [ tabname, i.title, k, scale, units, i ] ) + else: + self.plotNames.append( [ tabname, i.title, k, scale, units, i.field, i.ymin, i.ymax ] ) + if len( i.saveFile ) > 4 and i.saveFile[-4] == '.xml' or i.saveFile: + self.saveNames.append( [ tabname, len(self.saveNames), scale, units, i ] ) + + k += 1 + if i.field == 'n' or i.field == 'conc' or i.field == 'volume' or i.field == 'Gbar': + tabs = moose.Table2( tabname, numPlots ) + else: + tabs = moose.Table( tabname, numPlots ) + if i.field == 'spikeTime': + tabs.vec.threshold = -0.02 # Threshold for classifying Vm as a spike. + tabs.vec.useSpikeMode = True # spike detect mode on vtabs = moose.vec( tabs ) q = 0 @@ -731,8 +742,8 @@ class rdesigneur: moogliBase = moose.Neutral( self.modelPath + '/moogli' ) k = 0 for i in self.moogList: - kf = knownFields[i[3]] - pair = i[0] + " " + i[1] + kf = knownFields[i.field] + pair = i.elecpath + " " + i.geom_expr dendCompts = self.elecid.compartmentsFromExpression[ pair ] spineCompts = self.elecid.spinesFromExpression[ pair ] dendObj, mooField = self._parseComptField( dendCompts, i, knownFields ) @@ -740,13 +751,6 @@ class rdesigneur: assert( mooField == mooField2 ) mooObj3 = dendObj + spineObj numMoogli = len( mooObj3 ) - #dendComptMap = self.dendCompt.elecComptMap - #self.moogliViewer = rmoogli.makeMoogli( self, mooObj3, mooField ) - if len( i ) == 5: - i.extend( kf[4:6] ) - elif len( i ) == 6: - i.extend( [kf[5]] ) - #self.moogliViewer = rmoogli.makeMoogli( self, mooObj3, i, kf ) self.moogNames.append( rmoogli.makeMoogli( self, mooObj3, i, kf ) ) @@ -815,10 +819,15 @@ rdesigneur.rmoogli.updateMoogliViewer() plt.title( i[1] ) plt.xlabel( "position (voxels)" ) plt.ylabel( i[4] ) - mn = np.min(vpts) - mx = np.max(vpts) - if mn/mx < 0.3: - mn = 0 + plotinfo = i[5] + if plotinfo.ymin == plotinfo.ymax: + mn = np.min(vpts) + mx = np.max(vpts) + if mn/mx < 0.3: + mn = 0 + else: + mn = plotinfo.ymin + mx = plotinfo.ymax ax.set_ylim( mn, mx ) line, = plt.plot( range( len( vtab ) ), vpts[0] ) timeLabel = plt.text( len(vtab ) * 0.05, mn + 0.9*(mx-mn), 'time = 0' ) @@ -847,134 +856,13 @@ rdesigneur.rmoogli.updateMoogliViewer() Email address: sarthaks442@gmail.com Heavily modified by U.S. Bhalla ''' - - def _savePlots( self ): - if self.verbose: - print( 'rdesigneur: Saving plots ...', end = ' ' ) - sys.stdout.flush() - - knownFields = { - 'Vm':('CompartmentBase', 'getVm', 1000, 'Memb. Potential (mV)' ), - 'Cm':('CompartmentBase', 'getCm', 1e12, 'Memb. capacitance (pF)' ), - 'Rm':('CompartmentBase', 'getRm', 1e-9, 'Memb. Res (GOhm)' ), - 'Ra':('CompartmentBase', 'getRa', 1e-6, 'Axial Res (MOhm)' ), - 'spikeTime':('CompartmentBase', 'getVm', 1, 'Spike Times (s)'), - 'Im':('CompartmentBase', 'getIm', 1e9, 'Memb. current (nA)' ), - 'inject':('CompartmentBase', 'getInject', 1e9, 'inject current (nA)' ), - 'Gbar':('ChanBase', 'getGbar', 1e9, 'chan max conductance (nS)' ), - 'modulation':('ChanBase', 'getModulation', 1, 'chan modulation (unitless)' ), - 'Gk':('ChanBase', 'getGk', 1e9, 'chan conductance (nS)' ), - 'Ik':('ChanBase', 'getIk', 1e9, 'chan current (nA)' ), - 'ICa':('NMDAChan', 'getICa', 1e9, 'Ca current (nA)' ), - 'Ca':('CaConcBase', 'getCa', 1e3, 'Ca conc (uM)' ), - 'n':('PoolBase', 'getN', 1, '# of molecules'), - 'conc':('PoolBase', 'getConc', 1000, 'Concentration (uM)' ), - 'volume':('PoolBase', 'getVolume', 1e18, 'Volume (um^3)' ), - 'current':('VClamp', 'getCurrent', 1e9, 'Holding Current (nA)') - } - - save_graphs = moose.Neutral( self.modelPath + '/save_graphs' ) - dummy = moose.element( '/' ) - k = 0 - - for i in self.saveList: - pair = i[0] + " " + i[1] - dendCompts = self.elecid.compartmentsFromExpression[ pair ] - spineCompts = self.elecid.spinesFromExpression[ pair ] - plotObj, plotField = self._parseComptField( dendCompts, i, knownFields ) - plotObj2, plotField2 = self._parseComptField( spineCompts, i, knownFields ) - assert( plotField == plotField2 ) - plotObj3 = plotObj + plotObj2 - numPlots = sum( i != dummy for i in plotObj3 ) - if numPlots > 0: - save_tabname = save_graphs.path + '/save_plot' + str(k) - scale = knownFields[i[3]][2] - units = knownFields[i[3]][3] - self.saveNames.append( ( save_tabname, i[4], k, scale, units ) ) - k += 1 - if i[3] in [ 'n', 'conc', 'volume', 'Gbar' ]: - save_tabs = moose.Table2( save_tabname, numPlots ) - save_vtabs = moose.vec( save_tabs ) - else: - save_tabs = moose.Table( save_tabname, numPlots ) - save_vtabs = moose.vec( save_tabs ) - if i[3] == 'spikeTime': - save_vtabs.threshold = -0.02 # Threshold for classifying Vm as a spike. - save_vtabs.useSpikeMode = True # spike detect mode on - q = 0 - for p in [ x for x in plotObj3 if x != dummy ]: - moose.connect( save_vtabs[q], 'requestOut', p, plotField ) - q += 1 - - if self.verbose: - print( ' ... DONE.' ) - - def _getTimeSeriesTable( self ): - - ''' - This function gets the list with all the details of the simulation - required for plotting. - This function adds flexibility in terms of the details - we wish to store. - ''' - - knownFields = { - 'Vm':('CompartmentBase', 'getVm', 1000, 'Memb. Potential (mV)' ), - 'spikeTime':('CompartmentBase', 'getVm', 1, 'Spike Times (s)'), - 'Im':('CompartmentBase', 'getIm', 1e9, 'Memb. current (nA)' ), - 'inject':('CompartmentBase', 'getInject', 1e9, 'inject current (nA)' ), - 'Gbar':('ChanBase', 'getGbar', 1e9, 'chan max conductance (nS)' ), - 'Gk':('ChanBase', 'getGk', 1e9, 'chan conductance (nS)' ), - 'Ik':('ChanBase', 'getIk', 1e9, 'chan current (nA)' ), - 'ICa':('NMDAChan', 'getICa', 1e9, 'Ca current (nA)' ), - 'Ca':('CaConcBase', 'getCa', 1e3, 'Ca conc (uM)' ), - 'n':('PoolBase', 'getN', 1, '# of molecules'), - 'conc':('PoolBase', 'getConc', 1000, 'Concentration (uM)' ), - 'volume':('PoolBase', 'getVolume', 1e18, 'Volume (um^3)' ) - } - - ''' - This takes data from plotList - saveList is exactly like plotList but with a few additional arguments: - ->It will have a resolution option, i.e., the number of decimal figures to which the value should be rounded - ->There is a list of "saveAs" formats - With saveList, the user will able to set what all details he wishes to be saved. - ''' - - for i,ind in enumerate(self.saveNames): - pair = self.saveList[i][0] + " " + self.saveList[i][1] - dendCompts = self.elecid.compartmentsFromExpression[ pair ] - spineCompts = self.elecid.spinesFromExpression[ pair ] - # Here we get the object details from plotList - savePlotObj, plotField = self._parseComptField( dendCompts, self.saveList[i], knownFields ) - savePlotObj2, plotField2 = self._parseComptField( spineCompts, self.saveList[i], knownFields ) - savePlotObj3 = savePlotObj + savePlotObj2 - - rowList = list(ind) - save_vtab = moose.vec( ind[0] ) - t = np.arange( 0, save_vtab[0].vector.size, 1 ) * save_vtab[0].dt - - rowList.append(save_vtab[0].dt) - rowList.append(t) - rowList.append([jvec.vector * ind[3] for jvec in save_vtab]) #get values - rowList.append(self.saveList[i][3]) - rowList.append(filter(lambda obj: obj.path != '/', savePlotObj3)) #this filters out dummy elements - - if (type(self.saveList[i][-1])==int): - rowList.append(self.saveList[i][-1]) - else: - rowList.append(12) - - self.tabForXML.append(rowList) - rowList = [] - - timeSeriesTable = self.tabForXML # the list with all the details of plot - return timeSeriesTable - - def _writeXML( self, filename, timeSeriesData ): #to write to XML file - - plotData = timeSeriesData - print("[CAUTION] The '%s' file might be very large if all the compartments are to be saved." % filename) + def _writeXML( self, plotData, time, vtab ): + tabname = plotData[0] + idx = plotData[1] + scale = plotData[2] + units = plotData[3] + rp = plotData[4] + filename = rp.saveFile[:-4] + str(idx) + '.xml' root = etree.Element("TimeSeriesPlot") parameters = etree.SubElement( root, "parameters" ) if self.params == None: @@ -987,36 +875,33 @@ rdesigneur.rmoogli.updateMoogliViewer() #plotData contains all the details of a single plot title = etree.SubElement( root, "timeSeries" ) - title.set( 'title', str(plotData[1])) - title.set( 'field', str(plotData[8])) - title.set( 'scale', str(plotData[3])) - title.set( 'units', str(plotData[4])) - title.set( 'dt', str(plotData[5])) + title.set( 'title', rp.title) + title.set( 'field', rp.field) + title.set( 'scale', str(scale) ) + title.set( 'units', units) + title.set( 'dt', str(vtab[0].dt) ) + res = rp.saveResolution p = [] - assert(len(plotData[7]) == len(plotData[9])) - - res = plotData[10] - for ind, jvec in enumerate(plotData[7]): + for t, v in zip( time, vtab ): p.append( etree.SubElement( title, "data")) - p[-1].set( 'path', str(plotData[9][ind].path)) - p[-1].text = ''.join( str(round(value,res)) + ' ' for value in jvec ) + p[-1].set( 'path', v.path ) + p[-1].text = ''.join( str(round(y,res)) + ' ' for y in v.vector ) tree = etree.ElementTree(root) tree.write(filename) - def _writeCSV(self, filename, timeSeriesData): - - plotData = timeSeriesData - dataList = [] - header = [] - time = plotData[6] - res = plotData[10] - - for ind, jvec in enumerate(plotData[7]): - header.append(plotData[9][ind].path) - dataList.append([round(value,res) for value in jvec.tolist()]) - dl = [tuple(lst) for lst in dataList] - rows = zip(tuple(time), *dl) - header.insert(0, "time") + def _writeCSV( self, plotData, time, vtab ): + tabname = plotData[0] + idx = plotData[1] + scale = plotData[2] + units = plotData[3] + rp = plotData[4] + filename = rp.saveFile[:-4] + str(idx) + '.csv' + + header = ["time",] + valMatrix = [time,] + header.extend( [ v.path for v in vtab ] ) + valMatrix.extend( [ v.vector for v in vtab ] ) + nv = np.array( valMatrix ).T with open(filename, 'wb') as f: writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL) writer.writerow(header) @@ -1024,42 +909,25 @@ rdesigneur.rmoogli.updateMoogliViewer() writer.writerow(row) ##########****SAVING*****############### - def _saveFormats(self, timeSeriesData, k, *filenames): - "This takes in the filenames and writes to corresponding format." - if filenames: - for filename in filenames: - for name in filename: - print (name) - if name[-4:] == '.xml': - self._writeXML(name, timeSeriesData) - print(name, " written") - elif name[-4:] == '.csv': - self._writeCSV(name, timeSeriesData) - print(name, " written") - else: - print("Save format not known") - pass - else: - pass def _save( self ): - timeSeriesTable = self._getTimeSeriesTable() - for i,sList in enumerate(self.saveList): - - if (len(sList) >= 6) and (type(sList[5]) != int): - self.saveAs.extend(filter(lambda fmt: type(fmt)!=int, sList[5:])) - try: - timeSeriesData = timeSeriesTable[i] - except IndexError: - print("The object to be plotted has all dummy elements.") - pass - self._saveFormats(timeSeriesData, i, self.saveAs) - self.saveAs=[] + for i in self.saveNames: + tabname = i[0] + idx = i[1] + scale = i[2] + units = i[3] + rp = i[4] # The rplot data structure, it has the setup info. + + vtab = moose.vec( tabname ) + t = np.arange( 0, vtab[0].vector.size, 1 ) * vtab[0].dt + ftype = rp.filename[-4:] + if ftype == '.xml': + self._writeXML( i, t, vtab ) + elif ftype == '.csv': + self._writeCSV( i, t, vtab ) else: - pass - else: - pass + print("Save format '{}' not known, please use .csv or .xml".format( ftype ) ) ################################################################ # Here we set up the stims @@ -1080,17 +948,17 @@ rdesigneur.rmoogli.updateMoogliViewer() k = 0 # Stimlist = [path, geomExpr, relPath, field, expr_string] for i in self.stimList: - pair = i[0] + " " + i[1] + pair = i.elecpath + " " + i.geom_expr dendCompts = self.elecid.compartmentsFromExpression[ pair ] spineCompts = self.elecid.spinesFromExpression[ pair ] #print( "pair = {}, numcompts = {},{} ".format( pair, len( dendCompts), len( spineCompts ) ) ) - if i[3] == 'vclamp': + if i.field == 'vclamp': stimObj3 = self._buildVclampOnCompt( dendCompts, spineCompts, i ) stimField = 'commandIn' - elif i[3] == 'randsyn': + elif i.field == 'randsyn': stimObj3 = self._buildSynInputOnCompt( dendCompts, spineCompts, i ) stimField = 'setRate' - elif i[3] == 'periodicsyn': + elif i.field == 'periodicsyn': stimObj3 = self._buildSynInputOnCompt( dendCompts, spineCompts, i, doPeriodic = True ) stimField = 'setRate' else: @@ -1103,7 +971,7 @@ rdesigneur.rmoogli.updateMoogliViewer() funcname = stims.path + '/stim' + str(k) k += 1 func = moose.Function( funcname ) - func.expr = i[4] + func.expr = i.expr #if i[3] == 'vclamp': # Hack to clean up initial condition func.doEvalAtReinit = 1 for q in stimObj3: @@ -1619,3 +1487,95 @@ rdesigneur.rmoogli.updateMoogliViewer() for j in range( i[1], i[2] ): moose.connect( i[3], 'requestOut', chemVec[j], chemFieldSrc) msg = moose.connect( i[3], 'output', elObj, elecFieldDest ) + + +####################################################################### +# Some helper classes, used to define argument lists. +####################################################################### + +class baseplot: + def __init__( self, + elecpath='soma', geom_expr='1', relpath='.', field='Vm' ): + self.elecpath = elecpath + self.geom_expr = geom_expr + self.relpath = relpath + self.field = field + +class rplot( baseplot ): + def __init__( self, + elecpath = 'soma', geom_expr = '1', relpath = '.', field = 'Vm', + title = 'Membrane potential', + mode = 'time', + ymin = 0.0, ymax = 0.0, + saveFile = "", saveResolution = 3, show = True ): + baseplot.__init__( self, elecpath, geom_expr, relpath, field ) + self.title = title + self.mode = mode # Options: time, wave, wave_still, raster + self.ymin = ymin # If ymin == ymax, it autoscales. + self.ymax = ymax + if len( saveFile ) < 5: + self.saveFile = "" + else: + f = saveFile.split('.') + if len(f) < 2 or ( f[-1] != 'xml' and f[-1] != 'csv' ): + raise BuildError( "rplot: Filetype is '{}', must be of type .xml or .csv.".format( f[-1] ) ) + self.saveFile = saveFile + self.show = show + + def printme( self ): + print( "{}, {}, {}, {}, {}, {}, {}, {}, {}, {}".format( + self.elecpath, + self.geom_expr, self.relpath, self.field, self.title, + self.mode, self.ymin, self.ymax, self.saveFile, self.show ) ) + + @staticmethod + def convertArg( arg ): + if isinstance( arg, rplot ): + return arg + elif isinstance( arg, list ): + return rplot( *arg ) + else: + raise BuildError( "rplot initialization failed" ) + +class rmoog( baseplot ): + def __init__( self, + elecpath = 'soma', geom_expr = '1', relpath = '.', field = 'Vm', + title = 'Membrane potential', + ymin = 0.0, ymax = 0.0, + show = True ): # Could put in other display options. + baseplot.__init__( self, elecpath, geom_expr, relpath, field ) + self.title = title + self.ymin = ymin # If ymin == ymax, it autoscales. + self.ymax = ymax + self.show = show + + @staticmethod + def convertArg( arg ): + if isinstance( arg, rmoog ): + return arg + elif isinstance( arg, list ): + return rmoog( *arg ) + else: + raise BuildError( "rmoog initialization failed" ) + + # Stimlist = [path, geomExpr, relPath, field, expr_string] +class rstim( baseplot ): + def __init__( self, + elecpath = 'soma', geom_expr = '1', relpath = '.', field = 'inject', expr = '0'): + baseplot.__init__( self, elecpath, geom_expr, relpath, field ) + self.expr = expr + + def printme( self ): + print( "{}, {}, {}, {}, {}, {}, {}, {}, {}, {}".format( + self.elecpath, + self.geom_expr, self.relpath, self.field, self.expr ) ) + + @staticmethod + def convertArg( arg ): + if isinstance( arg, rstim ): + return arg + elif isinstance( arg, list ): + return rstim( *arg ) + else: + raise BuildError( "rstim initialization failed" ) + diff --git a/moose-core/python/rdesigneur/rmoogli.py b/moose-core/python/rdesigneur/rmoogli.py index 16317b5686aca2abb90e58faad963dcd17b21061..f673dad7ca0e975286e7e0c6dab39da9cc15967b 100644 --- a/moose-core/python/rdesigneur/rmoogli.py +++ b/moose-core/python/rdesigneur/rmoogli.py @@ -29,7 +29,7 @@ def makeMoogli( rd, mooObj, args, fieldInfo ): else: ymin = fieldInfo[4] ymax = fieldInfo[5] - print( "fieldinfo = {}, ymin = {}, ymax = {}".format( fieldInfo, ymin, ymax )) + #print( "fieldinfo = {}, ymin = {}, ymax = {}".format( fieldInfo, ymin, ymax )) viewer = moogul.MooView() if mooField == 'n' or mooField == 'conc': diff --git a/moose-core/tests/python/chem_models/19085.cspace b/moose-core/tests/python/chem_models/19085.cspace new file mode 100644 index 0000000000000000000000000000000000000000..1d2cf355c02f13483bdf5ed379ba58e4b823d30e --- /dev/null +++ b/moose-core/tests/python/chem_models/19085.cspace @@ -0,0 +1 @@ +M101: |DabX|Jbca| 5.59269 0.0157641 0.172865 0.361005 4.72728 1.08558 0.0982933 \ No newline at end of file diff --git a/moose-core/tests/python/testdisabled_dose_response.py b/moose-core/tests/python/testdisabled_dose_response.py new file mode 100644 index 0000000000000000000000000000000000000000..a840565aaccac9cf4bd64110da69dca07ff9c9a0 --- /dev/null +++ b/moose-core/tests/python/testdisabled_dose_response.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +## Makes and plots the dose response curve for bistable models +## Author: Sahil Moza +## June 26, 2014 +## Update: +## Friday 14 September 2018 05:48:42 PM IST +## Tunrned into a light-weight test by Dilawar Singh + +import os +import sys +import moose +import numpy as np + +sdir_ = os.path.dirname( os.path.realpath( __file__ ) ) + +def setupSteadyState(simdt,plotDt): + + ksolve = moose.Ksolve( '/model/kinetics/ksolve' ) + stoich = moose.Stoich( '/model/kinetics/stoich' ) + stoich.compartment = moose.element('/model/kinetics') + stoich.ksolve = ksolve + stoich.path = "/model/kinetics/##" + state = moose.SteadyState( '/model/kinetics/state' ) + moose.reinit() + state.stoich = stoich + state.showMatrices() + state.convergenceCriterion = 1e-8 + return ksolve, state + +def parseModelName(fileName): + pos1=fileName.rfind('/') + pos2=fileName.rfind('.') + directory=fileName[:pos1] + prefix=fileName[pos1+1:pos2] + suffix=fileName[pos2+1:len(fileName)] + return directory, prefix, suffix + +# Solve for the steady state +def getState( ksolve, state, vol): + scale = 1.0 / ( vol * 6.022e23 ) + moose.reinit() + state.randomInit() # Removing random initial condition to systematically make Dose reponse curves. + moose.start( 2.0 ) # Run the model for 2 seconds. + state.settle() + + vector = [] + a = moose.element( '/model/kinetics/a' ).conc + for x in ksolve.nVec[0]: + vector.append( x * scale) + failedSteadyState = any([np.isnan(x) for x in vector]) + if not (failedSteadyState): + return state.stateType, state.solutionStatus, a, vector + + +def main(): + # Setup parameters for simulation and plotting + simdt= 1e-2 + plotDt= 1 + + # Factors to change in the dose concentration in log scale + factorExponent = 10 ## Base: ten raised to some power. + factorBegin = -10 + factorEnd = 11 + factorStepsize = 2 + factorScale = 10.0 ## To scale up or down the factors + + # Load Model and set up the steady state solver. + # model = sys.argv[1] # To load model from a file. + model = os.path.join( sdir_, 'chem_models/19085.cspace' ) + modelPath, modelName, modelType = parseModelName(model) + outputDir = modelPath + + modelId = moose.loadModel(model, 'model', 'ee') + dosePath = '/model/kinetics/b/DabX' # The dose entity + + ksolve, state = setupSteadyState( simdt, plotDt) + vol = moose.element( '/model/kinetics' ).volume + iterInit = 100 + solutionVector = [] + factorArr = [] + + enz = moose.element(dosePath) + init = float(enz.kcat) # Dose parameter + + # Change Dose here to . + for factor in range(factorBegin, factorEnd, factorStepsize ): + scale = factorExponent ** (factor/factorScale) + enz.kcat = init * scale + print( "scale={:.3f}\tkcat={:.3f}".format( scale, enz.kcat) ) + for num in range(iterInit): + stateType, solStatus, a, vector = getState( ksolve, state, vol) + if solStatus == 0: + #solutionVector.append(vector[0]/sum(vector)) + solutionVector.append(a) + factorArr.append(scale) + + joint = np.array([factorArr, solutionVector]) + joint = joint[:,joint[1,:].argsort()] + got = np.mean( joint ), np.std( joint ) + expected = (1.2247, 2.46) + # Close upto 2 decimal place is good enough. + assert np.isclose(got, expected, atol=1e-2).all(), "Got %s, expected %s" % (got, expected) + print( joint ) + +if __name__ == '__main__': + main() diff --git a/moose-examples/.travis.yml b/moose-examples/.travis.yml index 0f209164e1c6fd9c79f41d8d6eef77a22550fbc6..46cb8a38d0d45115070f835661545650f12f7ae4 100644 --- a/moose-examples/.travis.yml +++ b/moose-examples/.travis.yml @@ -1,4 +1,9 @@ sudo : required +language: python +python: + - "2.7" + - "3.6" + - "3.7" notifications: email: @@ -7,16 +12,8 @@ notifications: - bhalla@ncbs.res.in - hrani@ncbs.res.in -install: - - sudo ./.travis_prepare.sh - script: + - pip install pymoose --user --pre - ./.travis_run.sh exclude: [vendor] - -deploy: - provider: pages - skip-cleanup: true - github-token: $GHI_TOKEN # - keep-history: true diff --git a/moose-examples/.travis_prepare.sh b/moose-examples/.travis_prepare.sh deleted file mode 100755 index a2ad83cc3a0099473bde794b6a81647d87016bf2..0000000000000000000000000000000000000000 --- a/moose-examples/.travis_prepare.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -# ROOT should run this script. -VERSION=$(lsb_release -r | cut -f2) -apt-get -y update -apt-get install cmake coreutils --force-yes -apt-get -y --force-yes install python-qt4 python-pip graphviz -apt-get -y --force-yes install python-h5py python-scipy python-pygraphviz -wget -nv https://download.opensuse.org/repositories/home:moose/xUbuntu_$VERSION/Release.key -O Release.key -apt-key add - < Release.key -cat <<EOF > /etc/apt/sources.list.d/home:moose.list -deb http://download.opensuse.org/repositories/home:/moose/xUbuntu_${VERSION}/ / -EOF -apt-get update -apt-get install python-numpy python-matplotlib python-networkx -apt-get install pymoose -python -m pip install python-libsbml --user diff --git a/moose-examples/neuroml2/NML2_SingleCompHHCell.nml b/moose-examples/neuroml2/NML2_SingleCompHHCell.nml new file mode 100644 index 0000000000000000000000000000000000000000..1b1d43b92606d2c1e0b78e88315c5d3b11b11271 --- /dev/null +++ b/moose-examples/neuroml2/NML2_SingleCompHHCell.nml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<neuroml xmlns="http://www.neuroml.org/schema/neuroml2" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.neuroml.org/schema/neuroml2 ../Schemas/NeuroML2/NeuroML_v2beta4.xsd" + id="NML2_SingleCompHHCell"> + + <!-- Single compartment cell with HH channels --> + + <!-- This is a "pure" NeuroML 2 file. It can be included in a LEMS file for use in a simulaton + by the LEMS interpreter, see LEMS_NML2_Ex5_DetCell.xml --> + + <ionChannelHH id="passiveChan" conductance="10pS"> + <notes>Leak conductance</notes> + </ionChannelHH> + + + <ionChannelHH id="naChan" conductance="10pS" species="na"> + <notes>Na channel</notes> + + <gateHHrates id="m" instances="3"> + <forwardRate type="HHExpLinearRate" rate="1per_ms" midpoint="-40mV" scale="10mV"/> + <reverseRate type="HHExpRate" rate="4per_ms" midpoint="-65mV" scale="-18mV"/> + </gateHHrates> + + <gateHHrates id="h" instances="1"> + <forwardRate type="HHExpRate" rate="0.07per_ms" midpoint="-65mV" scale="-20mV"/> + <reverseRate type="HHSigmoidRate" rate="1per_ms" midpoint="-35mV" scale="10mV"/> + </gateHHrates> + + </ionChannelHH> + + + <ionChannelHH id="kChan" conductance="10pS" species="k"> + + <gateHHrates id="n" instances="4"> + <forwardRate type="HHExpLinearRate" rate="0.1per_ms" midpoint="-55mV" scale="10mV"/> + <reverseRate type="HHExpRate" rate="0.125per_ms" midpoint="-65mV" scale="-80mV"/> + </gateHHrates> + + </ionChannelHH> + + + + <cell id="hhcell"> + + <morphology id="morph1"> + <segment id="0" name="soma"> + <proximal x="0" y="0" z="0" diameter="17.841242"/> <!--Gives a convenient surface area of 1000.0 um^2--> + <distal x="0" y="0" z="0" diameter="17.841242"/> + </segment> + + <segmentGroup id="soma_group"> + <member segment="0"/> + </segmentGroup> + + </morphology> + + <biophysicalProperties id="bioPhys1"> + + <membraneProperties> + + <channelDensity id="leak" ionChannel="passiveChan" condDensity="3.0 S_per_m2" erev="-54.3mV" ion="non_specific"/> + <channelDensity id="naChans" ionChannel="naChan" condDensity="120.0 mS_per_cm2" erev="50.0 mV" ion="na"/> + <channelDensity id="kChans" ionChannel="kChan" condDensity="360 S_per_m2" erev="-77mV" ion="k"/> + + <spikeThresh value="-20mV"/> + <specificCapacitance value="1.0 uF_per_cm2"/> + <initMembPotential value="-65mV"/> + + </membraneProperties> + + <intracellularProperties> + <resistivity value="0.03 kohm_cm"/> <!-- Note: not used in single compartment simulations --> + </intracellularProperties> + + </biophysicalProperties> + + </cell> + + <pulseGenerator id="pulseGen1" delay="100ms" duration="100ms" amplitude="0.08nA"/> + + + <network id="net1"> + <population id="hhpop" component="hhcell" size="1"/> + <explicitInput target="hhpop[0]" input="pulseGen1"/> + </network> + +</neuroml> + diff --git a/moose-examples/neuroml2/converter.py b/moose-examples/neuroml2/converter.py new file mode 100644 index 0000000000000000000000000000000000000000..0aec54f3ca940d66eabd83fd2d986208a29a2b03 --- /dev/null +++ b/moose-examples/neuroml2/converter.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +# converter.py --- +# +# Filename: mtoneuroml.py +# Description: +# Author: +# Maintainer: +# Created: Mon Apr 22 12:15:23 2013 (+0530) +# Version: +# Last-Updated: Wed Jul 10 16:36:14 2013 (+0530) +# By: subha +# Update #: 819 +# URL: +# Keywords: +# Compatibility: +# +# + +# Commentary: +# +# Utility for converting a MOOSE model into NeuroML2. This uses Python +# libNeuroML. +# +# + +# Change log: +# +# Tue May 21 16:58:03 IST 2013 - Subha moved the code for function +# fitting to hhfit.py. + +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, Fifth +# Floor, Boston, MA 02110-1301, USA. +# +# + +# Code: + +#!!!!! TODO: unit conversion !!!! + +try: + from future_builtins import zip +except ImportError: + pass +import traceback +import warnings +from collections import deque +import numpy as np +from scipy.optimize import curve_fit +from matplotlib import pyplot as plt + +import moose +from moose.utils import autoposition +import neuroml +import hhfit + + +def convert_morphology(root, positions='auto'): + """Convert moose neuron morphology contained under `root` into a + NeuroML object. The id of the return object is + {root.name}_morphology. Each segment object gets the numeric value + of the moose id of the object. The name of the segments are same + as the corresponding moose compartment. + + Parameters + ---------- + root : a moose element containing a single cell model. + + positions : string + flag to indicate if the positions of the end points of the + compartments are explicitly available in the compartments or + should be automatically generated. Possible values: + + `auto` - automatically generate z coordinates using length of the + compartments. + + `explicit` - model has explicit coordinates for all compartments. + + Return + ------ + a neuroml.Morphology instance. + + """ + if positions == 'auto': + queue = deque([autoposition(root)]) + elif positions == 'explicit': + compartments = moose.wildcardFind('%s/##[TYPE=Compartment]' % (root.path)) + queue = deque([compartment for compartment in map(moose.element, compartments) + if len(compartment.neighbours['axial']) == 0]) + if len(queue) != 1: + raise Exception('There must be one and only one top level compartment. Found %d' % (len(topcomp_list))) + else: + raise Exception('allowed values for keyword argument positions=`auto` or `explicit`') + comp_seg = {} + parent = None + while len(queue) > 0: + compartment = queue.popleft() + proximal = neuroml.Point3DWithDiam(x=compartment.x0, + y=compartment.y0, + z=compartment.z0, + diameter=compartment.diameter) + distal = neuroml.Point3DWithDiam(x=compartment.x, + y=compartment.y, + z=compartment.z, + diameter=compartment.diameter) + plist = list(map(moose.element, compartment.neighbours['axial'])) + try: + parent = neuroml.SegmentParent(segments=comp_seg[moose.element(plist[0])].id) + except (KeyError, IndexError) as e: + parent = None + segment = neuroml.Segment(id=compartment.id_.value, + proximal=proximal, + distal=distal, + parent=parent) + # TODO: For the time being using numerical value of the moose + # id for neuroml id.This needs to be updated for handling + # array elements + segment.name = compartment.name + comp_seg[compartment] = segment + queue.extend([comp for comp in map(moose.element, compartment.neighbours['raxial'])]) + morph = neuroml.Morphology(id='%s_morphology' % (root.name)) + morph.segments.extend(comp_seg.values()) + return morph + + +def define_vdep_rate(fn, name): + """Define new component type with generic expressions for voltage + dependent rate. + + """ + ctype = neuroml.ComponentType(name) + # This is going to be ugly ... + + + +def convert_hhgate(gate): + """Convert a MOOSE gate into GateHHRates in NeuroML""" + hh_rates = neuroml.GateHHRates(id=gate.id_.value, name=gate.name) + alpha = gate.tableA.copy() + beta = gate.tableB - alpha + vrange = np.linspace(gate.min, gate.max, len(alpha)) + afn, ap = hhfit.find_ratefn(vrange, alpha) + bfn, bp = hhfit.find_ratefn(vrange, beta) + if afn is None: + raise Exception('could not find a fitting function for `alpha`') + if bfn is None: + raise Exception('could not find a fitting function for `alpha`') + afn_type = fn_rate_map[afn] + afn_component_type = None + if afn_type is None: + afn_type, afn_component_type = define_component_type(afn) + hh_rates.forward_rate = neuroml.HHRate(type=afn_type, + midpoint='%gmV' % (ap[2]), + scale='%gmV' % (ap[1]), + rate='%gper_ms' % (ap[0])) + bfn_type = fn_rate_map[bfn] + bfn_component_type = None + if bfn_type is None: + bfn_type, bfn_component_type = define_component_type(bfn) + hh_rates.reverse_rate = neuroml.HHRate(type=bfn_type, + midpoint='%gmV' % (bp[2]), + scale='%gmV' % (bp[1]), + rate='%gper_ms' % (bp[0])) + return hh_rates, afn_component_type, bfn_component_type + + +def convert_hhchannel(channel): + """Convert a moose HHChannel object into a neuroml element. + + TODO: need to check useConcentration option for Ca2+ and V + dependent gates. How to handle generic expressions??? + + """ + nml_channel = neuroml.IonChannel(id=channel.id_.value + , name=channel.name, type='ionChannelHH' + , conductance=channel.Gbar + ) + if channel.Xpower > 0: + hh_rate_x = convert_hhgate(channel.gateX[0]) + hh_rate_x.instances = channel.Xpower + nml_channel.gate.append(hh_rate_x) + + if channel.Ypower > 0: + hh_rate_y = convert_hhgate(channel.gateY[0]) + hh_rate_y.instances = channel.Ypower + nml_channel.gate.append(hh_rate_y) + + if channel.Zpower > 0: + hh_rate_z = convert_hhgate(channel.gateZ[0]) + hh_rate_y.instances = channel.Zpower + nml_channel.gate.append(hh_rate_z) + return nml_channel + + +# +# converter.py ends here diff --git a/moose-examples/neuroml2/passiveCell.nml b/moose-examples/neuroml2/passiveCell.nml new file mode 100644 index 0000000000000000000000000000000000000000..9b91f2530dc08dd5a2d50b9e01f133b94552cf7a --- /dev/null +++ b/moose-examples/neuroml2/passiveCell.nml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<neuroml xmlns="http://www.neuroml.org/schema/neuroml2" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.neuroml.org/schema/neuroml2 ../Schemas/NeuroML2/NeuroML_v2beta4.xsd" + id="NML2_SingleCompHHCell"> + + + <ionChannel type="ionChannelPassive" id="passiveChan" conductance="10pS"> + <notes>Leak conductance</notes> + </ionChannel> + + <cell id="passiveCell"> + + <morphology id="morph1"> + <segment id="0" name="soma"> + <proximal x="0" y="0" z="0" diameter="17.841242"/> <!--Gives a convenient surface area of 1000.0 ?m�--> + <distal x="0" y="0" z="0" diameter="17.841242"/> + </segment> + + <segmentGroup id="soma_group"> + <member segment="0"/> + </segmentGroup> + + </morphology> + + <biophysicalProperties id="bioPhys1"> + + <membraneProperties> + <channelDensity id="leak" ionChannel="passiveChan" condDensity="3.0S_per_m2" erev="-54.3mV" ion="non_specific"/> + <spikeThresh value="-20mV"/> + <specificCapacitance value="1.0uF_per_cm2"/> + <initMembPotential value="-66.6mV"/> + </membraneProperties> + + <intracellularProperties> + <resistivity value="0.03kohm_cm"/> <!-- Note: not used in single compartment simulations --> + </intracellularProperties> + + </biophysicalProperties> + + </cell> + + <pulseGenerator id="pulseGen1" delay="50ms" duration="50ms" amplitude="0.08nA"/> + + + <network id="net1"> + <population id="pop0" component="passiveCell" size="1"/> + <explicitInput target="pop0[0]" input="pulseGen1"/> + </network> + +</neuroml> diff --git a/moose-examples/neuroml2/run_cell.py b/moose-examples/neuroml2/run_cell.py new file mode 100644 index 0000000000000000000000000000000000000000..174358d0b426fc0b2e3fa77232de8a4eb1c036b7 --- /dev/null +++ b/moose-examples/neuroml2/run_cell.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# run_cell.py --- +# +# Filename: run_cell.py +# Description: +# Author: +# Maintainer: P Gleeson +# Version: +# URL: +# Keywords: +# Compatibility: +# +# + +# Commentary: +# +# +# +# + +# Change log: +# Sunday 16 September 2018 10:04:24 AM IST +# - Tweaked file to to make it compatible with moose. +# +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, Fifth +# Floor, Boston, MA 02110-1301, USA. +# +# + +# Code: + +import moose +import sys +import numpy as np + + +def run(nogui): + + filename = 'passiveCell.nml' + print('Loading: %s'%filename) + reader = moose.mooseReadNML2( filename ) + assert reader + reader.read(filename) + + msoma = reader.getComp(reader.doc.networks[0].populations[0].id,0,0) + print(msoma) + + + data = moose.Neutral('/data') + + pg = reader.getInput('pulseGen1') + + inj = moose.Table('%s/pulse' % (data.path)) + moose.connect(inj, 'requestOut', pg, 'getOutputValue') + + + vm = moose.Table('%s/Vm' % (data.path)) + moose.connect(vm, 'requestOut', msoma, 'getVm') + + simdt = 1e-6 + plotdt = 1e-4 + simtime = 150e-3 + + if (1): + #moose.showmsg( '/clock' ) + for i in range(8): + moose.setClock( i, simdt ) + moose.setClock( 8, plotdt ) + moose.reinit() + else: + utils.resetSim([model.path, data.path], simdt, plotdt, simmethod='ee') + moose.showmsg( '/clock' ) + + moose.start(simtime) + + print("Finished simulation!") + + t = np.linspace(0, simtime, len(vm.vector)) + + if not nogui: + import matplotlib.pyplot as plt + + plt.subplot(211) + plt.plot(t, vm.vector * 1e3, label='Vm (mV)') + plt.legend() + plt.title('Vm') + plt.subplot(212) + plt.title('Input') + plt.plot(t, inj.vector * 1e9, label='injected (nA)') + #plt.plot(t, gK.vector * 1e6, label='K') + #plt.plot(t, gNa.vector * 1e6, label='Na') + plt.legend() + plt.show() + plt.close() + +if __name__ == '__main__': + nogui = '-nogui' in sys.argv + run(nogui) diff --git a/moose-examples/neuroml2/run_hhcell.py b/moose-examples/neuroml2/run_hhcell.py new file mode 100644 index 0000000000000000000000000000000000000000..2b38cd67d9f9ff8db70444582f4476d3f265e189 --- /dev/null +++ b/moose-examples/neuroml2/run_hhcell.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +# run_hhcell.py --- +# +# Filename: run_hhcell.py +# Description: +# Author: +# Maintainer: P Gleeson +# Version: +# URL: +# Keywords: +# Compatibility: +# +# + +# Commentary: +# +# +# +# + +# Change log: +# +# +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, Fifth +# Floor, Boston, MA 02110-1301, USA. +# +# + +# Code: + +import moose +import sys +import numpy as np + +def test_channel_gates(): + """Creates prototype channels under `/library` and plots the time + constants (tau) and activation (minf, hinf, ninf) parameters for the + channel gates. + + """ + import matplotlib.pyplot as plt + lib = moose.Neutral('/library') + m = moose.element('/library[0]/naChan[0]/gateX') + h = moose.element('/library[0]/naChan[0]/gateY') + n = moose.element('/library[0]/kChan[0]/gateX') + v = np.linspace(n.min,n.max, n.divs+1) + + plt.subplot(221) + plt.plot(v, 1/m.tableB, label='tau_m') + plt.plot(v, 1/h.tableB, label='tau_h') + plt.plot(v, 1/n.tableB, label='tau_n') + plt.legend() + + plt.subplot(222) + plt.plot(v, m.tableA/m.tableB, label='m_inf') + plt.plot(v, h.tableA/h.tableB, label='h_inf') + plt.plot(v, n.tableA/n.tableB, label='n_inf') + plt.legend() + + plt.subplot(223) + plt.plot(v, m.tableA, label='mA(alpha)') + plt.plot(v, h.tableA, label='hA(alpha)') + plt.plot(v, n.tableA, label='nA(alpha)') + plt.legend() + plt.subplot(224) + + plt.plot(v, m.tableB, label='mB') + plt.plot(v, m.tableB-m.tableA, label='mB-A(beta)') + + plt.plot(v, h.tableB, label='hB') + plt.plot(v, h.tableB-h.tableA, label='hB-A(beta)') + + plt.plot(v, n.tableB, label='nB') + plt.plot(v, n.tableB-n.tableA, label='nB-nA(beta)') + plt.legend() + + plt.show() + + +def run(nogui): + filename = 'NML2_SingleCompHHCell.nml' + print('Loading: %s'%filename) + reader = moose.mooseReadNML2(filename) + msoma = reader.getComp(reader.doc.networks[0].populations[0].id,0,0) + print(msoma) + data = moose.Neutral('/data') + pg = reader.getInput('pulseGen1') + inj = moose.Table('%s/pulse' % (data.path)) + moose.connect(inj, 'requestOut', pg, 'getOutputValue') + vm = moose.Table('%s/Vm' % (data.path)) + moose.connect(vm, 'requestOut', msoma, 'getVm') + + simdt = 1e-6 + plotdt = 1e-4 + simtime = 300e-3 + if (1): + #moose.showmsg( '/clock' ) + for i in range(8): + moose.setClock( i, simdt ) + moose.setClock( 8, plotdt ) + moose.reinit() + else: + utils.resetSim([model.path, data.path], simdt, plotdt, simmethod='ee') + moose.showmsg( '/clock' ) + moose.start(simtime) + + print("Finished simulation!") + + t = np.linspace(0, simtime, len(vm.vector)) + + if not nogui: + import matplotlib.pyplot as plt + + vfile = open('moose_v_hh.dat','w') + + for i in range(len(t)): + vfile.write('%s\t%s\n'%(t[i],vm.vector[i])) + vfile.close() + plt.subplot(211) + plt.plot(t, vm.vector * 1e3, label='Vm (mV)') + plt.legend() + plt.title('Vm') + plt.subplot(212) + plt.title('Input') + plt.plot(t, inj.vector * 1e9, label='injected (nA)') + #plt.plot(t, gK.vector * 1e6, label='K') + #plt.plot(t, gNa.vector * 1e6, label='Na') + plt.legend() + plt.figure() + test_channel_gates() + plt.show() + plt.close() + + +if __name__ == '__main__': + nogui = '-nogui' in sys.argv + run(nogui) diff --git a/moose-examples/squid/squid.py b/moose-examples/squid/squid.py index dae8ff9dc5e83e37bd6249a074965f140900cdc7..de3efc46ea16f41d1de9f58c26dda95e1f6c4058 100644 --- a/moose-examples/squid/squid.py +++ b/moose-examples/squid/squid.py @@ -28,8 +28,6 @@ # Code: import sys -sys.path.append('../../python') - import numpy import moose @@ -99,22 +97,24 @@ class IonChannel(moose.HHChannel): def alpha_m(self): if self.Xpower == 0: return numpy.array([]) - return numpy.array(moose.HHGate('%s/gateX' % (self.path)).tableA) + return numpy.array(moose.element('%s/gateX' % (self.path)).tableA) @property def beta_m(self): if self.Xpower == 0: return numpy.array([]) - return numpy.array(moose.HHGate('%s/gateX' % (self.path)).tableB) - numpy.array(moose.HHGate('%s/gateX' % (self.path)).tableA) + return numpy.array(moose.element('%s/gateX' % (self.path)).tableB) - \ + numpy.array(moose.element('%s/gateX' % (self.path)).tableA) @property def alpha_h(self): if self.Ypower == 0: return numpy.array([]) - return numpy.array(moose.HHGate('%s/gateY' % (self.path)).tableA) + return numpy.array(moose.element('%s/gateY' % (self.path)).tableA) @property def beta_h(self): if self.Ypower == 0: return numpy.array([]) - return numpy.array(moose.HHGate('%s/gateY' % (self.path)).tableB) - numpy.array(moose.HHGate('%s/gateY' % (self.path)).tableA) + return numpy.array(moose.element('%s/gateY' % (self.path)).tableB) \ + - numpy.array(moose.element('%s/gateY' % (self.path)).tableA) class SquidAxon(moose.Compartment): EREST_ACT = 0.0 # can be -70 mV if not following original HH convention diff --git a/moose-examples/squid/squid_demo.py b/moose-examples/squid/squid_demo.py index d243b0f5b03c88b0b84d20029cae8999008702c1..d87d615c922129db69130d691bdc73f35472af6c 100644 --- a/moose-examples/squid/squid_demo.py +++ b/moose-examples/squid/squid_demo.py @@ -1,5 +1,4 @@ - - # -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # squidgui.py --- # # Filename: squidgui.py @@ -48,18 +47,21 @@ # Code: import sys -sys.path.append('../../python') import os -os.environ['NUMPTHREADS'] = '1' - from collections import defaultdict import time -from PyQt4 import QtGui -from PyQt4 import QtCore +pyqt_ver_ = 4 +try: + from PyQt5 import QtGui, QtCore + pyqt_ver_ = 5 +except ImportError as e: + from PyQt4 import QtGui + from PyQt4 import QtCore import numpy from matplotlib.figure import Figure -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar +from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar import moose @@ -163,7 +165,6 @@ def set_default_line_edit_size(widget): widget.setMinimumSize(default_line_edit_size) widget.setMaximumSize(default_line_edit_size) - class SquidGui(QtGui.QMainWindow): defaults = {} defaults.update(SquidAxon.defaults) diff --git a/moose-examples/squid/squid_demo_qt5.py b/moose-examples/squid/squid_demo_qt5.py new file mode 100644 index 0000000000000000000000000000000000000000..59979883d8ff111d951ee70eb95c25f5d2173e47 --- /dev/null +++ b/moose-examples/squid/squid_demo_qt5.py @@ -0,0 +1,861 @@ +# -*- coding: utf-8 -*- +# Description: Squid Model +# Author: Subha +# Maintainer: Dilawar Singh <dilawars@ncbs.res.in> +# Created: Mon Jul 9 18:23:55 2012 (+0530) +# Version: +# Last-Updated: Wednesday 12 September 2018 04:23:52 PM IST +# PyQt5 version + +import sys +import os +from collections import defaultdict +import time + +from PyQt5 import QtGui, QtCore +from PyQt5.QtWidgets import QMainWindow, QApplication, QGroupBox, QSizePolicy +from PyQt5.QtWidgets import QLabel, QLineEdit, QGridLayout, QDockWidget +from PyQt5.QtWidgets import QCheckBox, QTabWidget, QComboBox, QWidget +from PyQt5.QtWidgets import QVBoxLayout, QFrame, QHBoxLayout, QAction +from PyQt5.QtWidgets import QToolButton, QScrollArea, QTextBrowser + +import numpy +from matplotlib.figure import Figure +from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar + +import moose + +from squid import * +from squid_setup import SquidSetup +from electronics import ClampCircuit + + +tooltip_Nernst = """<h3>Ionic equilibrium potential</h3> +<p/> +The equilibrium potential for ion C is given by Nernst equation: +<p> +E<sub>C</sub> = (RT/zF) * ln([C]<sub>out</sub> / [C]<sub>in</sub>) +</p> +where R is the ideal gas constant (8.3145 J/mol K),<br> + T is absolute temperature,<br> + z is the valence of the ion,<br> + F is Faraday's constant 96480 C/mol,<br> + [C]<sub>out</sub> is concentration of C outside the membrane,<br> + [C]<sub>in</sub> is concentration of C inside the membrane.""" + +tooltip_Erest = """<h3>Resting membrane potential</h3> +<p/> +The resting membrane potential is determined by the ionic +concentrations inside and outside the cell membrane and is given by +the Goldman-Hodgkin-Katz equation: +<p> + +V = (RT/F) * ln((P<sub>K</sub>[K<sup>+</sup>]<sub>out</sub> + P<sub>Na</sub>[Na<sup>+</sup>]<sub>out</sub> + P<sub>Cl</sub>[Cl<sup>-</sup>]<sub>in</sub>) / (P<sub>K</sub>[K<sup>+</sup>]in + P<sub>Na</sub>[Na<sup>+</sup>]<sub>in</sub> + P<sub>Cl</sub>[Cl<sup>-</sup>]<sub>out</sub>)) + +</p> +where P<sub>C</sub> is the permeability of the membrane to ion C. + +""" + +tooltip_NaChan = """<h3>Na+ channel conductance</h3> +<p/> +The Na<sup>+</sup> channel conductance in squid giant axon is given by: + +<p> G<sub>Na</sub> = Ḡ<sub>Na</sub> * m<sup>3</sup> * h </p> + +and the current through this channel is: +<p> +I<sub>Na</sub> = G<sub>Na</sub> * (V - E<sub>Na</sub>) = Ḡ<sub>Na</sub> * m<sup>3</sup> * h * (V - E<sub>Na</sub>) +</p> + +where Ḡ<sub>Na</sub> is the peak conductance of Na<sup>+</sup> channel, m is +the fraction of activation gates open and h is the fraction of +deactivation gates open. The transition from open to closed state has +first order kinetics: +<p> dm/dt = α<sub>m</sub> * ( 1 - m) - β<sub>m</sub> * m </p> +and similarly for h. + +The steady state values are: +<p> m<sub>∞</sub> = α<sub>m</sub>/(α<sub>m</sub> + β<sub>m</sub>) </p> +and time constant for steady state is: +<p>τ<sub>m</sub> = 1/ (α<sub>m</sub> + β<sub>m</sub>) </p> +and similarly for h. +""" + +tooltip_KChan = """<h3>K+ channel conductance</h3> +<p/>The K+ channel conductance in squid giant axon is given by: + +<p> G<sub>K</sub> = Ḡ<sub>K</sub> * n<sup>4</sup></p> + +and the current through this channel is: +<p> +I<sub>K</sub> = G<sub>K</sub> * (V - E<sub>K</sub>) = Ḡ<sub>K</sub> * n<sup>4</sup> * (V - E<sub>K</sub>) +</p> +where Ḡ<sub>K</sub> is the peak conductance of K<sup>+</sup> channel, +n is the fraction of activation gates open. The transition from open +to closed state has first order kinetics: <p> dn/dt = α<sub>n</sub> * +( 1 - n) - β<sub>n</sub> * n </p>. + +The steady state values are: +<p> +n<sub>∞</sub> = α<sub>n</sub>/(α<sub>n</sub> + β<sub>n</sub>) +</p> +and time constant for steady state is: +<p> +τ<sub>n</sub> = 1/ (α<sub>n</sub> + β<sub>n</sub>) + +</p> +and similarly for h. +""" + +tooltip_Im = """<h3>Membrane current</h3> +<p/> +The current through the membrane is given by: +<p> +I<sub>m</sub> = C<sub>m</sub> dV/dt + I<sub>K</sub> + I<sub>Na</sub> + I<sub>L</sub> +</p><p> + = C<sub>m</sub> dV/dt + G<sub>K</sub>(V, t) * (V - E<sub>K</sub>) + G<sub>Na</sub> * (V - E<sub>Na</sub>) + G<sub>L</sub> * (V - E<sub>L</sub>) +</p> +where G<sub>L</sub> is the leak current and E<sub>L</sub> is the leak reversal potential. + +""" + +default_line_edit_size = QtCore.QSize(80, 25) +def set_default_line_edit_size(widget): + widget.setMinimumSize(default_line_edit_size) + widget.setMaximumSize(default_line_edit_size) + +class SquidGui( QMainWindow ): + defaults = {} + defaults.update(SquidAxon.defaults) + defaults.update(ClampCircuit.defaults) + defaults.update({'runtime': 50.0, + 'simdt': 0.01, + 'plotdt': 0.1, + 'vclamp.holdingV': 0.0, + 'vclamp.holdingT': 10.0, + 'vclamp.prepulseV': 0.0, + 'vclamp.prepulseT': 0.0, + 'vclamp.clampV': 50.0, + 'vclamp.clampT': 20.0, + 'iclamp.baseI': 0.0, + 'iclamp.firstI': 0.1, + 'iclamp.firstT': 40.0, + 'iclamp.firstD': 5.0, + 'iclamp.secondI': 0.0, + 'iclamp.secondT': 0.0, + 'iclamp.secondD': 0.0 + }) + def __init__(self, *args): + QMainWindow.__init__(self, *args) + self.squid_setup = SquidSetup() + self._plotdt = SquidGui.defaults['plotdt'] + self._plot_dict = defaultdict(list) + self.setWindowTitle('Squid Axon simulation') + self.setDockNestingEnabled(True) + self._createRunControl() + self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self._runControlDock) + self._runControlDock.setFeatures(QDockWidget.AllDockWidgetFeatures) + self._createChannelControl() + self._channelCtrlBox.setWindowTitle('Channel properties') + self._channelControlDock.setFeatures(QDockWidget.AllDockWidgetFeatures) + self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self._channelControlDock) + self._createElectronicsControl() + self._electronicsDock.setFeatures(QDockWidget.AllDockWidgetFeatures) + self._electronicsDock.setWindowTitle('Electronics') + self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self._electronicsDock) + self._createPlotWidget() + self.setCentralWidget(self._plotWidget) + self._createStatePlotWidget() + self._createHelpMessage() + self._helpWindow.setVisible(False) + self._statePlotWidget.setWindowFlags(QtCore.Qt.Window) + self._statePlotWidget.setWindowTitle('State plot') + self._initActions() + self._createRunToolBar() + self._createPlotToolBar() + + def getFloatInput(self, widget, name): + try: + return float(str(widget.text())) + except ValueError: + QMessageBox.critical(self, 'Invalid input', 'Please enter a valid number for {}'.format(name)) + raise + + + def _createPlotWidget(self): + self._plotWidget = QWidget() + self._plotFigure = Figure() + self._plotCanvas = FigureCanvas(self._plotFigure) + self._plotCanvas.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self._plotCanvas.updateGeometry() + self._plotCanvas.setParent(self._plotWidget) + self._plotCanvas.mpl_connect('scroll_event', self._onScroll) + self._plotFigure.set_canvas(self._plotCanvas) + # Vm and command voltage go in the same subplot + self._vm_axes = self._plotFigure.add_subplot(2,2,1, title='Membrane potential') + self._vm_axes.set_ylim(-20.0, 120.0) + # Channel conductances go to the same subplot + self._g_axes = self._plotFigure.add_subplot(2,2,2, title='Channel conductance') + self._g_axes.set_ylim(0.0, 0.5) + # Injection current for Vclamp/Iclamp go to the same subplot + self._im_axes = self._plotFigure.add_subplot(2,2,3, title='Injection current') + self._im_axes.set_ylim(-0.5, 0.5) + # Channel currents go to the same subplot + self._i_axes = self._plotFigure.add_subplot(2,2,4, title='Channel current') + self._i_axes.set_ylim(-10, 10) + for axis in self._plotFigure.axes: + axis.set_autoscale_on(False) + layout = QVBoxLayout() + layout.addWidget(self._plotCanvas) + self._plotNavigator = NavigationToolbar(self._plotCanvas, self._plotWidget) + layout.addWidget(self._plotNavigator) + self._plotWidget.setLayout(layout) + + def _createStatePlotWidget(self): + self._statePlotWidget = QWidget() + self._statePlotFigure = Figure() + self._statePlotCanvas = FigureCanvas(self._statePlotFigure) + self._statePlotCanvas.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self._statePlotCanvas.updateGeometry() + self._statePlotCanvas.setParent(self._statePlotWidget) + self._statePlotFigure.set_canvas(self._statePlotCanvas) + self._statePlotFigure.subplots_adjust(hspace=0.5) + self._statePlotAxes = self._statePlotFigure.add_subplot(2,1,1, title='State plot') + self._state_plot, = self._statePlotAxes.plot([], [], label='state') + self._activationParamAxes = self._statePlotFigure.add_subplot(2,1,2, title='H-H activation parameters vs time') + self._activationParamAxes.set_xlabel('Time (ms)') + #for axis in self._plotFigure.axes: + # axis.autoscale(False) + self._stateplot_xvar_label = QLabel('Variable on X-axis') + self._stateplot_xvar_combo = QComboBox() + self._stateplot_xvar_combo.addItems(['V', 'm', 'n', 'h']) + self._stateplot_xvar_combo.setCurrentIndex(0) + self._stateplot_xvar_combo.setEditable(False) + # self.connect(self._stateplot_xvar_combo, + # QtCore.SIGNAL('currentIndexChanged(const QString&)'), + # self._statePlotXSlot) + self._stateplot_xvar_combo.currentIndexChanged.connect( self._statePlotXSlot ) + self._stateplot_yvar_label = QLabel('Variable on Y-axis') + self._stateplot_yvar_combo = QComboBox() + self._stateplot_yvar_combo.addItems(['V', 'm', 'n', 'h']) + self._stateplot_yvar_combo.setCurrentIndex(2) + self._stateplot_yvar_combo.setEditable(False) + self._stateplot_yvar_combo.currentIndexChanged.connect(self._statePlotYSlot) + self._statePlotNavigator = NavigationToolbar(self._statePlotCanvas, self._statePlotWidget) + frame = QFrame() + frame.setFrameStyle(QFrame.StyledPanel + QFrame.Raised) + layout = QHBoxLayout() + layout.addWidget(self._stateplot_xvar_label) + layout.addWidget(self._stateplot_xvar_combo) + layout.addWidget(self._stateplot_yvar_label) + layout.addWidget(self._stateplot_yvar_combo) + frame.setLayout(layout) + self._closeStatePlotAction = QAction('Close', self) + self._closeStatePlotAction.triggered.connect(self._statePlotWidget.close) + self._closeStatePlotButton = QToolButton() + self._closeStatePlotButton.setDefaultAction(self._closeStatePlotAction) + layout = QVBoxLayout() + layout.addWidget(frame) + layout.addWidget(self._statePlotCanvas) + layout.addWidget(self._statePlotNavigator) + layout.addWidget(self._closeStatePlotButton) + self._statePlotWidget.setLayout(layout) + # Setting the close event so that when the help window is + # closed the ``State plot`` button becomes unchecked + self._statePlotWidget.closeEvent = lambda event: self._showStatePlotAction.setChecked(False) + + def _createRunControl(self): + self._runControlBox = QGroupBox(self) + self._runControlBox.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + self._runTimeLabel = QLabel("Run time (ms)", self._runControlBox) + self._simTimeStepLabel = QLabel("Simulation time step (ms)", self._runControlBox) + self._runTimeEdit = QLineEdit('%g' % (SquidGui.defaults['runtime']), self._runControlBox) + set_default_line_edit_size(self._runTimeEdit) + self._simTimeStepEdit = QLineEdit('%g' % (SquidGui.defaults['simdt']), self._runControlBox) + set_default_line_edit_size(self._simTimeStepEdit) + layout = QGridLayout() + layout.addWidget(self._runTimeLabel, 0, 0) + layout.addWidget(self._runTimeEdit, 0, 1) + layout.addWidget(self._simTimeStepLabel, 1, 0) + layout.addWidget(self._simTimeStepEdit, 1, 1) + layout.setColumnStretch(2, 1.0) + layout.setRowStretch(2, 1.0) + self._runControlBox.setLayout(layout) + self._runControlDock = QDockWidget('Simulation', self) + self._runControlDock.setWidget(self._runControlBox) + + def _createChannelControl(self): + self._channelControlDock = QDockWidget('Channels', self) + self._channelCtrlBox = QGroupBox(self) + self._naConductanceToggle = QCheckBox('Block Na+ channel', self._channelCtrlBox) + self._naConductanceToggle.setToolTip('<html>%s</html>' % (tooltip_NaChan)) + self._kConductanceToggle = QCheckBox('Block K+ channel', self._channelCtrlBox) + self._kConductanceToggle.setToolTip('<html>%s</html>' % (tooltip_KChan)) + self._kOutLabel = QLabel('[K+]out (mM)', self._channelCtrlBox) + self._kOutEdit = QLineEdit('%g' % (self.squid_setup.squid_axon.K_out), + self._channelCtrlBox) + self._kOutLabel.setToolTip('<html>%s</html>' % (tooltip_Nernst)) + self._kOutEdit.setToolTip('<html>%s</html>' % (tooltip_Nernst)) + set_default_line_edit_size(self._kOutEdit) + self._naOutLabel = QLabel('[Na+]out (mM)', self._channelCtrlBox) + self._naOutEdit = QLineEdit('%g' % (self.squid_setup.squid_axon.Na_out), + self._channelCtrlBox) + self._naOutLabel.setToolTip('<html>%s</html>' % (tooltip_Nernst)) + self._naOutEdit.setToolTip('<html>%s</html>' % (tooltip_Nernst)) + set_default_line_edit_size(self._naOutEdit) + self._kInLabel = QLabel('[K+]in (mM)', self._channelCtrlBox) + self._kInEdit = QLineEdit('%g' % (self.squid_setup.squid_axon.K_in), + self._channelCtrlBox) + self._kInEdit.setToolTip(tooltip_Nernst) + self._naInLabel = QLabel('[Na+]in (mM)', self._channelCtrlBox) + self._naInEdit = QLineEdit('%g' % (self.squid_setup.squid_axon.Na_in), + self._channelCtrlBox) + self._naInEdit.setToolTip('<html>%s</html>' % (tooltip_Nernst)) + self._temperatureLabel = QLabel('Temperature (C)', self._channelCtrlBox) + self._temperatureEdit = QLineEdit('%g' % (self.defaults['temperature'] - CELSIUS_TO_KELVIN), + self._channelCtrlBox) + self._temperatureEdit.setToolTip('<html>%s</html>' % (tooltip_Nernst)) + set_default_line_edit_size(self._temperatureEdit) + for child in self._channelCtrlBox.children(): + if isinstance(child, QLineEdit): + set_default_line_edit_size(child) + layout = QGridLayout(self._channelCtrlBox) + layout.addWidget(self._naConductanceToggle, 0, 0) + layout.addWidget(self._kConductanceToggle, 1, 0) + layout.addWidget(self._naOutLabel, 2, 0) + layout.addWidget(self._naOutEdit, 2, 1) + layout.addWidget(self._naInLabel, 3, 0) + layout.addWidget(self._naInEdit, 3, 1) + layout.addWidget(self._kOutLabel, 4, 0) + layout.addWidget(self._kOutEdit, 4, 1) + layout.addWidget(self._kInLabel, 5, 0) + layout.addWidget(self._kInEdit, 5, 1) + layout.addWidget(self._temperatureLabel, 6, 0) + layout.addWidget(self._temperatureEdit, 6, 1) + layout.setRowStretch(7, 1.0) + self._channelCtrlBox.setLayout(layout) + self._channelControlDock.setWidget(self._channelCtrlBox) + return self._channelCtrlBox + + def __get_stateplot_data(self, name): + data = [] + if name == 'V': + data = self.squid_setup.vm_table.vector + elif name == 'm': + data = self.squid_setup.m_table.vector + elif name == 'h': + data = self.squid_setup.h_table.vector + elif name == 'n': + data = self.squid_setup.n_table.vector + else: + raise ValueError('Unrecognized selection: %s' % (name)) + return numpy.asarray(data) + + def _statePlotYSlot(self, selectedItem): + ydata = self.__get_stateplot_data(str(selectedItem)) + self._state_plot.set_ydata(ydata) + self._statePlotAxes.set_ylabel(selectedItem) + if str(selectedItem) == 'V': + self._statePlotAxes.set_ylim(-20, 120) + else: + self._statePlotAxes.set_ylim(0, 1) + self._statePlotCanvas.draw() + + def _statePlotXSlot(self, selectedItem): + xdata = self.__get_stateplot_data(str(selectedItem)) + self._state_plot.set_xdata(xdata) + self._statePlotAxes.set_xlabel(selectedItem) + if str(selectedItem) == 'V': + self._statePlotAxes.set_xlim(-20, 120) + else: + self._statePlotAxes.set_xlim(0, 1) + self._statePlotCanvas.draw() + + def _createElectronicsControl(self): + """Creates a tabbed widget of voltage clamp and current clamp controls""" + self._electronicsTab = QTabWidget(self) + self._electronicsTab.addTab(self._getIClampCtrlBox(), 'Current clamp') + self._electronicsTab.addTab(self._getVClampCtrlBox(), 'Voltage clamp') + self._electronicsDock = QDockWidget(self) + self._electronicsDock.setWidget(self._electronicsTab) + + def _getVClampCtrlBox(self): + vClampPanel = QGroupBox(self) + self._vClampCtrlBox = vClampPanel + self._holdingVLabel = QLabel("Holding Voltage (mV)", vClampPanel) + self._holdingVEdit = QLineEdit('%g' % (SquidGui.defaults['vclamp.holdingV']), vClampPanel) + self._holdingTimeLabel = QLabel("Holding Time (ms)", vClampPanel) + self._holdingTimeEdit = QLineEdit('%g' % (SquidGui.defaults['vclamp.holdingT']), vClampPanel) + self._prePulseVLabel = QLabel("Pre-pulse Voltage (mV)", vClampPanel) + self._prePulseVEdit = QLineEdit('%g' % (SquidGui.defaults['vclamp.prepulseV']), vClampPanel) + self._prePulseTimeLabel = QLabel("Pre-pulse Time (ms)", vClampPanel) + self._prePulseTimeEdit = QLineEdit('%g' % (SquidGui.defaults['vclamp.prepulseT']), vClampPanel) + self._clampVLabel = QLabel("Clamp Voltage (mV)", vClampPanel) + self._clampVEdit = QLineEdit('%g' % (SquidGui.defaults['vclamp.clampV']), vClampPanel) + self._clampTimeLabel = QLabel("Clamp Time (ms)", vClampPanel) + self._clampTimeEdit = QLineEdit('%g' % (SquidGui.defaults['vclamp.clampT']), vClampPanel) + for child in vClampPanel.children(): + if isinstance(child, QLineEdit): + set_default_line_edit_size(child) + layout = QGridLayout(vClampPanel) + layout.addWidget(self._holdingVLabel, 0, 0) + layout.addWidget(self._holdingVEdit, 0, 1) + layout.addWidget(self._holdingTimeLabel, 1, 0) + layout.addWidget(self._holdingTimeEdit, 1, 1) + layout.addWidget(self._prePulseVLabel, 2, 0) + layout.addWidget(self._prePulseVEdit, 2, 1) + layout.addWidget(self._prePulseTimeLabel,3,0) + layout.addWidget(self._prePulseTimeEdit, 3, 1) + layout.addWidget(self._clampVLabel, 4, 0) + layout.addWidget(self._clampVEdit, 4, 1) + layout.addWidget(self._clampTimeLabel, 5, 0) + layout.addWidget(self._clampTimeEdit, 5, 1) + layout.setRowStretch(6, 1.0) + vClampPanel.setLayout(layout) + return self._vClampCtrlBox + + def _getIClampCtrlBox(self): + iClampPanel = QGroupBox(self) + self._iClampCtrlBox = iClampPanel + self._baseCurrentLabel = QLabel("Base Current Level (uA)",iClampPanel) + self._baseCurrentEdit = QLineEdit('%g' % (SquidGui.defaults['iclamp.baseI']),iClampPanel) + self._firstPulseLabel = QLabel("First Pulse Current (uA)", iClampPanel) + self._firstPulseEdit = QLineEdit('%g' % (SquidGui.defaults['iclamp.firstI']), iClampPanel) + self._firstDelayLabel = QLabel("First Onset Delay (ms)", iClampPanel) + self._firstDelayEdit = QLineEdit('%g' % (SquidGui.defaults['iclamp.firstD']),iClampPanel) + self._firstPulseWidthLabel = QLabel("First Pulse Width (ms)", iClampPanel) + self._firstPulseWidthEdit = QLineEdit('%g' % (SquidGui.defaults['iclamp.firstT']), iClampPanel) + self._secondPulseLabel = QLabel("Second Pulse Current (uA)", iClampPanel) + self._secondPulseEdit = QLineEdit('%g' % (SquidGui.defaults['iclamp.secondI']), iClampPanel) + self._secondDelayLabel = QLabel("Second Onset Delay (ms)", iClampPanel) + self._secondDelayEdit = QLineEdit('%g' % (SquidGui.defaults['iclamp.secondD']),iClampPanel) + self._secondPulseWidthLabel = QLabel("Second Pulse Width (ms)", iClampPanel) + self._secondPulseWidthEdit = QLineEdit('%g' % (SquidGui.defaults['iclamp.secondT']), iClampPanel) + self._pulseMode = QComboBox(iClampPanel) + self._pulseMode.addItem("Single Pulse") + self._pulseMode.addItem("Pulse Train") + for child in iClampPanel.children(): + if isinstance(child, QLineEdit): + set_default_line_edit_size(child) + layout = QGridLayout(iClampPanel) + layout.addWidget(self._baseCurrentLabel, 0, 0) + layout.addWidget(self._baseCurrentEdit, 0, 1) + layout.addWidget(self._firstPulseLabel, 1, 0) + layout.addWidget(self._firstPulseEdit, 1, 1) + layout.addWidget(self._firstDelayLabel, 2, 0) + layout.addWidget(self._firstDelayEdit, 2, 1) + layout.addWidget(self._firstPulseWidthLabel, 3, 0) + layout.addWidget(self._firstPulseWidthEdit, 3, 1) + layout.addWidget(self._secondPulseLabel, 4, 0) + layout.addWidget(self._secondPulseEdit, 4, 1) + layout.addWidget(self._secondDelayLabel, 5, 0) + layout.addWidget(self._secondDelayEdit, 5, 1) + layout.addWidget(self._secondPulseWidthLabel, 6, 0) + layout.addWidget(self._secondPulseWidthEdit, 6, 1) + layout.addWidget(self._pulseMode, 7, 0, 1, 2) + layout.setRowStretch(8, 1.0) + # layout.setSizeConstraint(QLayout.SetFixedSize) + iClampPanel.setLayout(layout) + return self._iClampCtrlBox + + def _overlayPlots(self, overlay): + if not overlay: + for axis in (self._plotFigure.axes + self._statePlotFigure.axes): + title = axis.get_title() + axis.clear() + axis.set_title(title) + suffix = '' + else: + suffix = '_%d' % (len(self._plot_dict['vm'])) + self._vm_axes.set_xlim(0.0, self._runtime) + self._g_axes.set_xlim(0.0, self._runtime) + self._im_axes.set_xlim(0.0, self._runtime) + self._i_axes.set_xlim(0.0, self._runtime) + self._vm_plot, = self._vm_axes.plot([], [], label='Vm%s'%(suffix)) + self._plot_dict['vm'].append(self._vm_plot) + self._command_plot, = self._vm_axes.plot([], [], label='command%s'%(suffix)) + self._plot_dict['command'].append(self._command_plot) + # Channel conductances go to the same subplot + self._gna_plot, = self._g_axes.plot([], [], label='Na%s'%(suffix)) + self._plot_dict['gna'].append(self._gna_plot) + self._gk_plot, = self._g_axes.plot([], [], label='K%s'%(suffix)) + self._plot_dict['gk'].append(self._gk_plot) + # Injection current for Vclamp/Iclamp go to the same subplot + self._iclamp_plot, = self._im_axes.plot([], [], label='Iclamp%s'%(suffix)) + self._vclamp_plot, = self._im_axes.plot([], [], label='Vclamp%s'%(suffix)) + self._plot_dict['iclamp'].append(self._iclamp_plot) + self._plot_dict['vclamp'].append(self._vclamp_plot) + # Channel currents go to the same subplot + self._ina_plot, = self._i_axes.plot([], [], label='Na%s'%(suffix)) + self._plot_dict['ina'].append(self._ina_plot) + self._ik_plot, = self._i_axes.plot([], [], label='K%s'%(suffix)) + self._plot_dict['ik'].append(self._ik_plot) + # self._i_axes.legend() + # State plots + self._state_plot, = self._statePlotAxes.plot([], [], label='state%s'%(suffix)) + self._plot_dict['state'].append(self._state_plot) + self._m_plot, = self._activationParamAxes.plot([],[], label='m%s'%(suffix)) + self._h_plot, = self._activationParamAxes.plot([], [], label='h%s'%(suffix)) + self._n_plot, = self._activationParamAxes.plot([], [], label='n%s'%(suffix)) + self._plot_dict['m'].append(self._m_plot) + self._plot_dict['h'].append(self._h_plot) + self._plot_dict['n'].append(self._n_plot) + if self._showLegendAction.isChecked(): + for axis in (self._plotFigure.axes + self._statePlotFigure.axes): + axis.legend() + + def _updateAllPlots(self): + self._updatePlots() + self._updateStatePlot() + + def _updatePlots(self): + if len(self.squid_setup.vm_table.vector) <= 0: + return + vm = numpy.asarray(self.squid_setup.vm_table.vector) + cmd = numpy.asarray(self.squid_setup.cmd_table.vector) + ik = numpy.asarray(self.squid_setup.ik_table.vector) + ina = numpy.asarray(self.squid_setup.ina_table.vector) + iclamp = numpy.asarray(self.squid_setup.iclamp_table.vector) + vclamp = numpy.asarray(self.squid_setup.vclamp_table.vector) + gk = numpy.asarray(self.squid_setup.gk_table.vector) + gna = numpy.asarray(self.squid_setup.gna_table.vector) + time_series = numpy.linspace(0, self._plotdt * len(vm), len(vm)) + self._vm_plot.set_data(time_series, vm) + time_series = numpy.linspace(0, self._plotdt * len(cmd), len(cmd)) + self._command_plot.set_data(time_series, cmd) + time_series = numpy.linspace(0, self._plotdt * len(ik), len(ik)) + self._ik_plot.set_data(time_series, ik) + time_series = numpy.linspace(0, self._plotdt * len(ina), len(ina)) + self._ina_plot.set_data(time_series, ina) + time_series = numpy.linspace(0, self._plotdt * len(iclamp), len(iclamp)) + self._iclamp_plot.set_data(time_series, iclamp) + time_series = numpy.linspace(0, self._plotdt * len(vclamp), len(vclamp)) + self._vclamp_plot.set_data(time_series, vclamp) + time_series = numpy.linspace(0, self._plotdt * len(gk), len(gk)) + self._gk_plot.set_data(time_series, gk) + time_series = numpy.linspace(0, self._plotdt * len(gna), len(gna)) + self._gna_plot.set_data(time_series, gna) + # self._vm_axes.margins(y=0.1) + # self._g_axes.margin(y=0.1) + # self._im_axes.margins(y=0.1) + # self._i_axes.margins(y=0.1) + if self._autoscaleAction.isChecked(): + for axis in self._plotFigure.axes: + axis.relim() + axis.margins(0.1, 0.1) + axis.autoscale_view(tight=True) + else: + self._vm_axes.set_ylim(-20.0, 120.0) + self._g_axes.set_ylim(0.0, 0.5) + self._im_axes.set_ylim(-0.5, 0.5) + self._i_axes.set_ylim(-10, 10) + self._vm_axes.set_xlim(0.0, time_series[-1]) + self._g_axes.set_xlim(0.0, time_series[-1]) + self._im_axes.set_xlim(0.0, time_series[-1]) + self._i_axes.set_xlim(0.0, time_series[-1]) + self._plotCanvas.draw() + + def _updateStatePlot(self): + if len(self.squid_setup.vm_table.vector) <= 0: + return + sx = str(self._stateplot_xvar_combo.currentText()) + sy = str(self._stateplot_yvar_combo.currentText()) + xdata = self.__get_stateplot_data(sx) + ydata = self.__get_stateplot_data(sy) + minlen = min(len(xdata), len(ydata)) + self._state_plot.set_data(xdata[:minlen], ydata[:minlen]) + self._statePlotAxes.set_xlabel(sx) + self._statePlotAxes.set_ylabel(sy) + if sx == 'V': + self._statePlotAxes.set_xlim(-20, 120) + else: + self._statePlotAxes.set_xlim(0, 1) + if sy == 'V': + self._statePlotAxes.set_ylim(-20, 120) + else: + self._statePlotAxes.set_ylim(0, 1) + self._activationParamAxes.set_xlim(0, self._runtime) + m = self.__get_stateplot_data('m') + n = self.__get_stateplot_data('n') + h = self.__get_stateplot_data('h') + time_series = numpy.linspace(0, self._plotdt*len(m), len(m)) + self._m_plot.set_data(time_series, m) + time_series = numpy.linspace(0, self._plotdt*len(h), len(h)) + self._h_plot.set_data(time_series, h) + time_series = numpy.linspace(0, self._plotdt*len(n), len(n)) + self._n_plot.set_data(time_series, n) + if self._autoscaleAction.isChecked(): + for axis in self._statePlotFigure.axes: + axis.relim() + axis.set_autoscale_on(True) + axis.autoscale_view(True) + self._statePlotCanvas.draw() + + def _runSlot(self): + if moose.isRunning(): + print('Stopping simulation in progress ...') + moose.stop() + self._runtime = self.getFloatInput(self._runTimeEdit, self._runTimeLabel.text()) + self._overlayPlots(self._overlayAction.isChecked()) + self._simdt = self.getFloatInput(self._simTimeStepEdit, self._simTimeStepLabel.text()) + clampMode = None + singlePulse = True + if self._electronicsTab.currentWidget() == self._vClampCtrlBox: + clampMode = 'vclamp' + baseLevel = self.getFloatInput(self._holdingVEdit, self._holdingVLabel.text()) + firstDelay = self.getFloatInput(self._holdingTimeEdit, self._holdingTimeLabel.text()) + firstWidth = self.getFloatInput(self._prePulseTimeEdit, self._prePulseTimeLabel.text()) + firstLevel = self.getFloatInput(self._prePulseVEdit, self._prePulseVLabel.text()) + secondDelay = firstWidth + secondWidth = self.getFloatInput(self._clampTimeEdit, self._clampTimeLabel.text()) + secondLevel = self.getFloatInput(self._clampVEdit, self._clampVLabel.text()) + if not self._autoscaleAction.isChecked(): + self._im_axes.set_ylim(-10.0, 10.0) + else: + clampMode = 'iclamp' + baseLevel = self.getFloatInput(self._baseCurrentEdit, self._baseCurrentLabel.text()) + firstDelay = self.getFloatInput(self._firstDelayEdit, self._firstDelayLabel.text()) + firstWidth = self.getFloatInput(self._firstPulseWidthEdit, self._firstPulseWidthLabel.text()) + firstLevel = self.getFloatInput(self._firstPulseEdit, self._firstPulseLabel.text()) + secondDelay = self.getFloatInput(self._secondDelayEdit, self._secondDelayLabel.text()) + secondLevel = self.getFloatInput(self._secondPulseEdit, self._secondPulseLabel.text()) + secondWidth = self.getFloatInput(self._secondPulseWidthEdit, self._secondPulseWidthLabel.text()) + singlePulse = (self._pulseMode.currentIndex() == 0) + if not self._autoscaleAction.isChecked(): + self._im_axes.set_ylim(-0.4, 0.4) + self.squid_setup.clamp_ckt.configure_pulses(baseLevel=baseLevel, + firstDelay=firstDelay, + firstWidth=firstWidth, + firstLevel=firstLevel, + secondDelay=secondDelay, + secondWidth=secondWidth, + secondLevel=secondLevel, + singlePulse=singlePulse) + if self._kConductanceToggle.isChecked(): + self.squid_setup.squid_axon.specific_gK = 0.0 + else: + self.squid_setup.squid_axon.specific_gK = SquidAxon.defaults['specific_gK'] + if self._naConductanceToggle.isChecked(): + self.squid_setup.squid_axon.specific_gNa = 0.0 + else: + self.squid_setup.squid_axon.specific_gNa = SquidAxon.defaults['specific_gNa'] + self.squid_setup.squid_axon.celsius = self.getFloatInput(self._temperatureEdit, self._temperatureLabel.text()) + self.squid_setup.squid_axon.K_out = self.getFloatInput(self._kOutEdit, self._kOutLabel.text()) + self.squid_setup.squid_axon.Na_out = self.getFloatInput(self._naOutEdit, self._naOutLabel.text()) + self.squid_setup.squid_axon.K_in = self.getFloatInput(self._kInEdit, self._kInLabel.text()) + self.squid_setup.squid_axon.Na_in = self.getFloatInput(self._naInEdit, self._naInLabel.text()) + self.squid_setup.squid_axon.updateEk() + self.squid_setup.schedule(self._simdt, self._plotdt, clampMode) + # The following line is for use with Qthread + self.squid_setup.run(self._runtime) + self._updateAllPlots() + + def _toggleDocking(self, on): + self._channelControlDock.setFloating(on) + self._electronicsDock.setFloating(on) + self._runControlDock.setFloating(on) + + def _restoreDocks(self): + self._channelControlDock.setVisible(True) + self._electronicsDock.setVisible(True) + self._runControlDock.setVisible(True) + + def _initActions(self): + self._runAction = QAction(self.tr('Run'), self) + self._runAction.setShortcut(self.tr('F5')) + self._runAction.setToolTip('Run simulation (F5)') + self._runAction.triggered.connect( self._runSlot) + self._resetToDefaultsAction = QAction(self.tr('Restore defaults'), self) + self._resetToDefaultsAction.setToolTip('Reset all settings to their default values') + self._resetToDefaultsAction.triggered.connect( self._useDefaults) + self._showLegendAction = QAction(self.tr('Display legend'), self) + self._showLegendAction.setCheckable(True) + self._showLegendAction.toggled.connect(self._showLegend) + self._showStatePlotAction = QAction(self.tr('State plot'), self) + self._showStatePlotAction.setCheckable(True) + self._showStatePlotAction.setChecked(False) + self._showStatePlotAction.toggled.connect(self._statePlotWidget.setVisible) + self._autoscaleAction = QAction(self.tr('Auto-scale plots'), self) + self._autoscaleAction.setCheckable(True) + self._autoscaleAction.setChecked(False) + self._autoscaleAction.toggled.connect(self._autoscale) + self._overlayAction = QAction('Overlay plots', self) + self._overlayAction.setCheckable(True) + self._overlayAction.setChecked(False) + self._dockAction = QAction('Undock all', self) + self._dockAction.setCheckable(True) + self._dockAction.setChecked(False) + # self._dockAction.toggle.connect( self._toggleDocking) + self._restoreDocksAction = QAction('Show all', self) + self._restoreDocksAction.triggered.connect( self._restoreDocks) + self._quitAction = QAction(self.tr('&Quit'), self) + self._quitAction.setShortcut(self.tr('Ctrl+Q')) + self._quitAction.triggered.connect(qApp.closeAllWindows) + + + + def _createRunToolBar(self): + self._simToolBar = self.addToolBar(self.tr('Simulation control')) + self._simToolBar.addAction(self._quitAction) + self._simToolBar.addAction(self._runAction) + self._simToolBar.addAction(self._resetToDefaultsAction) + self._simToolBar.addAction(self._dockAction) + self._simToolBar.addAction(self._restoreDocksAction) + + def _createPlotToolBar(self): + self._plotToolBar = self.addToolBar(self.tr('Plotting control')) + self._plotToolBar.addAction(self._showLegendAction) + self._plotToolBar.addAction(self._autoscaleAction) + self._plotToolBar.addAction(self._overlayAction) + self._plotToolBar.addAction(self._showStatePlotAction) + self._plotToolBar.addAction(self._helpAction) + self._plotToolBar.addAction(self._helpBiophysicsAction) + + def _showLegend(self, on): + if on: + for axis in (self._plotFigure.axes + self._statePlotFigure.axes): + axis.legend().set_visible(True) + else: + for axis in (self._plotFigure.axes + self._statePlotFigure.axes): + axis.legend().set_visible(False) + self._plotCanvas.draw() + self._statePlotCanvas.draw() + + def _autoscale(self, on): + if on: + for axis in (self._plotFigure.axes + self._statePlotFigure.axes): + axis.relim() + axis.set_autoscale_on(True) + axis.autoscale_view(True) + else: + for axis in self._plotFigure.axes: + axis.set_autoscale_on(False) + self._vm_axes.set_ylim(-20.0, 120.0) + self._g_axes.set_ylim(0.0, 0.5) + self._im_axes.set_ylim(-0.5, 0.5) + self._i_axes.set_ylim(-10, 10) + self._plotCanvas.draw() + self._statePlotCanvas.draw() + + def _useDefaults(self): + self._runTimeEdit.setText('%g' % (self.defaults['runtime'])) + self._simTimeStepEdit.setText('%g' % (self.defaults['simdt'])) + self._overlayAction.setChecked(False) + self._naConductanceToggle.setChecked(False) + self._kConductanceToggle.setChecked(False) + self._kOutEdit.setText('%g' % (SquidGui.defaults['K_out'])) + self._naOutEdit.setText('%g' % (SquidGui.defaults['Na_out'])) + self._kInEdit.setText('%g' % (SquidGui.defaults['K_in'])) + self._naInEdit.setText('%g' % (SquidGui.defaults['Na_in'])) + self._temperatureEdit.setText('%g' % (SquidGui.defaults['temperature'] - CELSIUS_TO_KELVIN)) + self._holdingVEdit.setText('%g' % (SquidGui.defaults['vclamp.holdingV'])) + self._holdingTimeEdit.setText('%g' % (SquidGui.defaults['vclamp.holdingT'])) + self._prePulseVEdit.setText('%g' % (SquidGui.defaults['vclamp.prepulseV'])) + self._prePulseTimeEdit.setText('%g' % (SquidGui.defaults['vclamp.prepulseT'])) + self._clampVEdit.setText('%g' % (SquidGui.defaults['vclamp.clampV'])) + self._clampTimeEdit.setText('%g' % (SquidGui.defaults['vclamp.clampT'])) + self._baseCurrentEdit.setText('%g' % (SquidGui.defaults['iclamp.baseI'])) + self._firstPulseEdit.setText('%g' % (SquidGui.defaults['iclamp.firstI'])) + self._firstDelayEdit.setText('%g' % (SquidGui.defaults['iclamp.firstD'])) + self._firstPulseWidthEdit.setText('%g' % (SquidGui.defaults['iclamp.firstT'])) + self._secondPulseEdit.setText('%g' % (SquidGui.defaults['iclamp.secondI'])) + self._secondDelayEdit.setText('%g' % (SquidGui.defaults['iclamp.secondD'])) + self._secondPulseWidthEdit.setText('%g' % (SquidGui.defaults['iclamp.secondT'])) + self._pulseMode.setCurrentIndex(0) + + def _onScroll(self, event): + if event.inaxes is None: + return + axes = event.inaxes + zoom = 0.0 + if event.button == 'up': + zoom = -1.0 + elif event.button == 'down': + zoom = 1.0 + if zoom != 0.0: + self._plotNavigator.push_current() + axes.get_xaxis().zoom(zoom) + axes.get_yaxis().zoom(zoom) + self._plotCanvas.draw() + + def closeEvent(self, event): + qApp.closeAllWindows() + + def _showBioPhysicsHelp(self): + self._createHelpMessage() + self._helpMessageText.setText('<html><p>%s</p><p>%s</p><p>%s</p><p>%s</p><p>%s</p></html>' % + (tooltip_Nernst, + tooltip_Erest, + tooltip_KChan, + tooltip_NaChan, + tooltip_Im)) + self._helpWindow.setVisible(True) + + def _showRunningHelp(self): + self._createHelpMessage() + self._helpMessageText.setSource(QtCore.QUrl(self._helpBaseURL)) + self._helpWindow.setVisible(True) + + def _createHelpMessage(self): + if hasattr(self, '_helpWindow'): + return + self._helpWindow = QWidget() + self._helpWindow.setWindowFlags(QtCore.Qt.Window) + layout = QVBoxLayout() + self._helpWindow.setLayout(layout) + self._helpMessageArea = QScrollArea() + self._helpMessageText = QTextBrowser() + self._helpMessageText.setOpenExternalLinks(True) + self._helpMessageArea.setWidget(self._helpMessageText) + layout.addWidget(self._helpMessageText) + self._squidGuiPath = os.path.dirname(os.path.abspath(__file__)) + self._helpBaseURL = os.path.join(self._squidGuiPath,'help.html') + self._helpMessageText.setSource(QtCore.QUrl(self._helpBaseURL)) + self._helpMessageText.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self._helpMessageArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self._helpMessageText.setMinimumSize(800, 600) + self._closeHelpAction = QAction('Close', self) + self._closeHelpAction.triggered.connect(self._helpWindow.close) + # Setting the close event so that the ``Help`` button is + # unchecked when the help window is closed + self._helpWindow.closeEvent = lambda event: self._helpAction.setChecked(False) + self._helpTOCAction = QAction('Help running demo', self) + self._helpTOCAction.triggered.connect( self._jumpToHelpTOC) + # This panel is for putting two buttons using horizontal + # layout + panel = QFrame() + panel.setFrameStyle(QFrame.StyledPanel + QFrame.Raised) + layout.addWidget(panel) + layout = QHBoxLayout() + panel.setLayout(layout) + self._helpAction = QAction('Help running', self) + self._helpAction.triggered.connect(self._showRunningHelp) + self._helpBiophysicsAction = QAction('Help biophysics', self) + self._helpBiophysicsAction.triggered.connect(self._showBioPhysicsHelp) + self._helpTOCButton = QToolButton() + self._helpTOCButton.setDefaultAction(self._helpTOCAction) + self._helpBiophysicsButton = QToolButton() + self._helpBiophysicsButton.setDefaultAction(self._helpBiophysicsAction) + layout.addWidget(self._helpTOCButton) + layout.addWidget(self._helpBiophysicsButton) + self._closeHelpButton = QToolButton() + self._closeHelpButton.setDefaultAction(self._closeHelpAction) + layout.addWidget(self._closeHelpButton) + + def _jumpToHelpTOC(self): + self._helpMessageText.setSource(QtCore.QUrl(self._helpBaseURL)) + +if __name__ == '__main__': + app = QApplication(sys.argv) + # app.connect(app, QtCore.SIGNAL('lastWindowClosed()'), app, QtCore.SLOT('quit()')) + app.quitOnLastWindowClosed( ) + qApp = app + squid_gui = SquidGui() + squid_gui.show() + print(squid_gui.size()) + sys.exit(app.exec_()) + +# +# squidgui.py ends here diff --git a/moose-examples/squid/test_squid.py b/moose-examples/squid/test_squid.py index 005cb1626077b6164a00f7eb17ec0c788fa3e670..5bbef8bcec843677903be28e162c44e514633238 100644 --- a/moose-examples/squid/test_squid.py +++ b/moose-examples/squid/test_squid.py @@ -30,6 +30,7 @@ import unittest import pylab +import numpy from squid import SquidModel class SquidAxonTest(unittest.TestCase): @@ -112,6 +113,5 @@ class SquidAxonTest(unittest.TestCase): self.assertLessEqual(difference, numpy.mean(beta_m)*1e-6) - # # test_squid.py ends here diff --git a/moose-examples/tutorials/Electrophys/CableInjectEquivCkt.png b/moose-examples/tutorials/Electrophys/CableInjectEquivCkt.png new file mode 100644 index 0000000000000000000000000000000000000000..d1938db8fdf25b373a13fce83b704d5edd031040 Binary files /dev/null and b/moose-examples/tutorials/Electrophys/CableInjectEquivCkt.png differ diff --git a/moose-examples/tutorials/Electrophys/README.txt b/moose-examples/tutorials/Electrophys/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..585fd34eb33aea4c68345cc080ba48190fdd7090 --- /dev/null +++ b/moose-examples/tutorials/Electrophys/README.txt @@ -0,0 +1,80 @@ +This is a list of files in the Electrophys tutorials directory + +ephys1_cable.py: + +This is a model of a simple uniform passive cable. It has two plots: one +of membrane potential Vm as a function of time, sampled at four locations +along the cable, and the other of Vm as a function of position, sampled at +5 times (intervals of 5 ms) during the simulation. +The user can use the sliders to scale the following parameters up and down: +RM +RA +CM +length +diameter + +In addition, the user can toggle between a long (steady-state) current +injection stimulus, and a brief pulse of 1 ms. + +Finally, when the user hits "Quit" the time-series of the soma compartment +is printed out. + +Things to do: +1. Vary diameter, length, RM, CM, RA. Check that the decay behaves as per + cable theory. Get an intuition for these. +2. Check that lambda (length constant) is according to equations. Check + how much decay there is for different L (electrotonic length). +2a. Note that if the cable is shorter than L, the signal doesn't decay so + well. Why? +2b. Convince yourself that you'll never see a passive signal along a long + axon. +3. Examine propagation of the depolarization for a brief current pulse. + Look for the three signatures of propagation along a dendrite. +4. Run a simulation of the long stimulus. When you hit Quit, the program will + dump the soma potential charging time-course into a file. Check that + the tau (time-constant) of the soma is according to equations when the + L is very small, but deviates when it is longer. Use Rall's expression + for L as a function of the time-courses of soma depolarization. + + + +ephys2_Rall_law.py: + +This explores the implication of Rall's Law and cable branching. +This is a model of a branched cell, compared with the model of a uniform +cylindrical cell. The sliders vary the parameters of the branches. Two +time-points are displayed; 10 ms and 50 ms. + +Things to do: +1. Vary all the passive parameters of branches, see how they affect the + propagation past the branch point. +2. Match up the branched cell (blue plot/dots) to the cylinder (red line). + See if the resultant parameters obey Rall's Law. + +Todo: - Stimuli in any end or both dend ends. + +Neuronal summation + - Synaptic input at Y tips and branch point, both I and E. + - Vary weights + - Vary time since start for each. + - Vary taus of I. + - Have spiking soma, to set up thresholding as an option. + +Channel mixer: + - Modulate the conductance of battery of channels + - Modulate Ca_conc tau + - Plot Ca and Vm + - Set different stim pulse amplitude and duration. + +NMDA receptor and associativity: + - Plot Vm and Ca + - Give glu and NMDA input different time and different ampl + +AP propagation: + - Vary dia + - Vary RM, CM, RA + - Vary channel densities + +Squid demo: + - Already have it. + diff --git a/moose-examples/tutorials/Electrophys/RallsLaw.png b/moose-examples/tutorials/Electrophys/RallsLaw.png new file mode 100644 index 0000000000000000000000000000000000000000..21fae9010f07863d14dd3a3cf25720f8dfe6846d Binary files /dev/null and b/moose-examples/tutorials/Electrophys/RallsLaw.png differ diff --git a/moose-examples/tutorials/Electrophys/ephys1_cable.py b/moose-examples/tutorials/Electrophys/ephys1_cable.py new file mode 100644 index 0000000000000000000000000000000000000000..515ea4fda7ca2b9fbbbd33e4023d264eb902a8b2 --- /dev/null +++ b/moose-examples/tutorials/Electrophys/ephys1_cable.py @@ -0,0 +1,212 @@ +######################################################################## +# This example demonstrates a cable +# Copyright (C) Upinder S. Bhalla NCBS 2018 +# Released under the terms of the GNU Public License V3. +######################################################################## +import matplotlib.pyplot as plt +import matplotlib.image as mpimg +from matplotlib.widgets import Slider, Button +import numpy as np +import warnings +import moose +import rdesigneur as rd + +numDendSeg = 50 +interval = 0.005 +lines = [] +tplot = [] +RM = 1.0 +RA = 1.0 +CM = 0.01 +length = 0.002 +dia = 1e-6 +stimStr = "2e-10" +runtime = 50e-3 +elecPlotDt = 0.001 + +def makeModel(): + segLen = length / numDendSeg + rdes = rd.rdesigneur( + stealCellFromLibrary = True, + elecPlotDt = elecPlotDt, + verbose = False, + # cellProto syntax: ['ballAndStick', 'name', somaDia, somaLength, dendDia, dendLength, numDendSegments ] + # The numerical arguments are all optional + cellProto = + [['ballAndStick', 'soma', dia, segLen, dia, length, numDendSeg]], + passiveDistrib = [[ '#', 'RM', str(RM), 'CM', str(CM), 'RA', str(RA) ]], + stimList = [['soma', '1', '.', 'inject', stimStr ]], + plotList = [['dend3,dend18,dend33,dend49', '1', '.', 'Vm' ]], + ) + rdes.buildModel() + +def main(): + global vec + warnings.filterwarnings("ignore", category=UserWarning, module="matplotlib") + makeDisplay() + quit() + +def updateRM(RM_): + global RM + RM = RM_ + updateDisplay() + +def updateCM(CM_): + global CM + CM = CM_ + updateDisplay() + +def updateRA(RA_): + global RA + RA = RA_ + updateDisplay() + +def updateLen(val): # This does the length of the entire cell. + global length + length = val * 0.001 + updateDisplay() + +def updateDia(val): + global dia + dia = val * 1e-6 + updateDisplay() + +class stimToggle(): + def __init__( self, toggle, ax ): + self.duration = 1 + self.toggle = toggle + self.ax = ax + + def click( self, event ): + global stimStr + if self.duration < 0.5: + self.duration = 1.0 + self.toggle.label.set_text( "Long Stim" ) + self.toggle.color = "yellow" + self.toggle.hovercolor = "yellow" + stimStr = "2e-10" + else: + self.duration = 0.001 + self.toggle.label.set_text( "Short Stim" ) + self.toggle.color = "orange" + self.toggle.hovercolor = "orange" + stimStr = "1e-9*(t<0.001)" + #stimStr = "10e-9*(t<0.001)-9e-9*(t>0.001&&t<0.002)" + updateDisplay() + +def updateDisplay(): + makeModel() + vec = moose.wildcardFind( '/model/elec/#[ISA=CompartmentBase]' ) + tabvec = moose.wildcardFind( '/model/graphs/plot#' ) + #vec[0].inject = 1e-10 + moose.reinit() + initdt = 0.0001 + dt = initdt + currtime = 0.0 + for i in lines: + moose.start( dt ) + currtime += dt + i.set_ydata( [v.Vm * 1000 for v in vec] ) + dt = interval + #print( "inRunSim v0={}, v10={}".format( vec[0].Vm, vec[10].Vm ) ) + moose.start( runtime - currtime ) + for i,tab in zip( tplot, tabvec ): + i.set_ydata( tab.vector * 1000 ) + + moose.delete( '/model' ) + moose.delete( '/library' ) + # Put in something here for the time-series on soma + +def doQuit( event ): + tab = [] + makeModel() + soma = moose.element( '/model/elec/soma' ) + moose.reinit() + with open('output.txt', 'w') as file: + file.write( "0.000 {:.2f}\n".format( soma.Vm * 1000 ) ) + for t in np.arange( 0, 0.1, 0.001 ): + moose.start(0.001) + file.write( "{:.3f} {:.2f}\n".format( t+0.001, soma.Vm*1000 ) ) + quit() + +def makeDisplay(): + global tplot + global lines + #img = mpimg.imread( 'CableEquivCkt.png' ) + img = mpimg.imread( 'CableInjectEquivCkt.png' ) + #plt.ion() + fig = plt.figure( figsize=(10,12) ) + png = fig.add_subplot(321) + imgplot = plt.imshow( img ) + plt.axis('off') + ax1 = fig.add_subplot(322) + plt.ylabel( 'Vm (mV)' ) + plt.ylim( -80, 40 ) + plt.xlabel( 'time (ms)' ) + plt.title( "Membrane potential vs time at 4 positions." ) + t = np.arange( 0.0, runtime + elecPlotDt / 2.0, elecPlotDt ) * 1000 #ms + for i,col,name in zip( range( 4 ), ['b-', 'g-', 'r-', 'm-' ], ['a', 'b', 'c', 'd'] ): + ln, = ax1.plot( t, np.zeros(len(t)), col, label='pos= ' + name ) + tplot.append(ln) + plt.legend() + + ax2 = fig.add_subplot(312) + #ax2.margins( 0.05 ) + #ax.set_ylim( 0, 0.1 ) + plt.ylabel( 'Vm (mV)' ) + plt.ylim( -80, 50 ) + plt.xlabel( 'Position (microns)' ) + #ax2.autoscale( enable = True, axis = 'y' ) + plt.title( "Membrane potential vs position, at 5 times." ) + t = np.arange( 0, numDendSeg+1, 1 ) #sec + for i,col in zip( range( 5 ), ['k-', 'b-', 'g-', 'y-', 'm-' ] ): + ln, = ax2.plot( t, np.zeros(numDendSeg+1), col, label="t={}".format(i*interval) ) + lines.append(ln) + plt.legend() + ax = fig.add_subplot(313) + #ax.margins( 0.05 ) + plt.axis('off') + axcolor = 'palegreen' + axStim = plt.axes( [0.02,0.05, 0.20,0.03], facecolor='green' ) + axReset = plt.axes( [0.25,0.05, 0.30,0.03], facecolor='blue' ) + axQuit = plt.axes( [0.60,0.05, 0.30,0.03], facecolor='blue' ) + axRM = plt.axes( [0.25,0.1, 0.65,0.03], facecolor=axcolor ) + axCM = plt.axes( [0.25,0.15, 0.65,0.03], facecolor=axcolor ) + axRA = plt.axes( [0.25,0.20, 0.65,0.03], facecolor=axcolor ) + axLen = plt.axes( [0.25,0.25, 0.65,0.03], facecolor=axcolor ) + axDia = plt.axes( [0.25,0.30, 0.65,0.03], facecolor=axcolor ) + #aInit = Slider( axAinit, 'A init conc', 0, 10, valinit=1.0, valstep=0.2) + stim = Button( axStim, 'Long Stim', color = 'yellow' ) + stimObj = stimToggle( stim, axStim ) + + reset = Button( axReset, 'Reset', color = 'cyan' ) + q = Button( axQuit, 'Quit', color = 'pink' ) + RM = Slider( axRM, 'RM ( ohm.m^2 )', 0.1, 10, valinit=1.0 ) + CM = Slider( axCM, 'CM ( Farad/m^2)', 0.001, 0.1, valinit=0.01 ) + RA = Slider( axRA, 'RA ( ohm.m', 0.1, 10, valinit=1.0 ) + length = Slider( axLen, 'Total length of cell (mm)', 0.1, 10, valinit=2.0 ) + dia = Slider( axDia, 'Diameter of cell (um)', 0.1, 10, valinit=1.0 ) + def resetParms( event ): + RM.reset() + CM.reset() + RA.reset() + length.reset() + dia.reset() + + + stim.on_clicked( stimObj.click ) + reset.on_clicked( resetParms ) + q.on_clicked( doQuit ) + RM.on_changed( updateRM ) + CM.on_changed( updateCM ) + RA.on_changed( updateRA ) + length.on_changed( updateLen ) + dia.on_changed( updateDia ) + plt.tight_layout() + + updateDisplay() + plt.show() + +# Run the 'main' if this script is executed standalone. +if __name__ == '__main__': + main() diff --git a/moose-examples/tutorials/Electrophys/ephys2_Rall_law.py b/moose-examples/tutorials/Electrophys/ephys2_Rall_law.py new file mode 100644 index 0000000000000000000000000000000000000000..a5f6e90d25f5716473cc474319097a7fc80150d9 --- /dev/null +++ b/moose-examples/tutorials/Electrophys/ephys2_Rall_law.py @@ -0,0 +1,268 @@ +######################################################################## +# This example demonstrates a cable +# Copyright (C) Upinder S. Bhalla NCBS 2018 +# Released under the terms of the GNU Public License V3. +######################################################################## +import moose +import matplotlib.pyplot as plt +import matplotlib.image as mpimg +from matplotlib.widgets import Slider, Button +import numpy as np +import rdesigneur as rd + +numDendSeg = 10 # Applies both to dend and to branches. +interval1 = 0.010 +interval2 = 0.05 - interval1 +lines = [] +# These 5 params vary only for the branches. +RM = 1.0 +RA = 1.0 +CM = 0.01 +length = 0.001 +dia = 2e-6 +# The params below are fixed, apply to the soma and dend. +dendRM = 2.0 +dendRA = 1.5 +dendCM = 0.02 +dendLength = 0.001 +dendDia = 2e-6 + +# Stimulus in Amps applied to soma. +stimStr = "2e-10" + +class lineWrapper(): + def __init__( self ): + self.YdendLines = 0 + self.Ybranch1Lines = 0 + self.Ybranch2Lines = 0 + self.CylLines = 0 + +def makeYmodel(): + segLen = dendLength / numDendSeg + rdes = rd.rdesigneur( + stealCellFromLibrary = True, + verbose = False, + # cellProto syntax: ['ballAndStick', 'name', somaDia, somaLength, dendDia, dendLength, numDendSegments ] + # The numerical arguments are all optional + cellProto = + [['ballAndStick', 'cellBase', dendDia, segLen, dendDia, dendLength, numDendSeg]], + passiveDistrib = [[ '#', 'RM', str(dendRM), 'CM', str(dendCM), 'RA', str(dendRA) ]], + stimList = [['soma', '1', '.', 'inject', stimStr ]], + ) + # Build the arms of the Y for a branching cell. + pa = moose.element( '/library/cellBase' ) + x = length + y = 0.0 + dx = length / ( numDendSeg * np.sqrt(2.0) ) + dy = 0.0 + prevc1 = moose.element( '/library/cellBase/dend{}'.format( numDendSeg-1 ) ) + prevc2 = prevc1 + for i in range( numDendSeg ): + c1 = rd.buildCompt( pa, 'branch1_{}'.format(i), RM = RM, CM = CM, RA = RA, dia = dia, x=x, y=y, dx = dx, dy = dy ) + c2 = rd.buildCompt( pa, 'branch2_{}'.format(i), RM = RM, CM = CM, RA = RA, dia = dia, x=x, y=-y, dx = dx, dy = -dy ) + moose.connect( prevc1, 'axial', c1, 'raxial' ) + moose.connect( prevc2, 'axial', c2, 'raxial' ) + prevc1 = c1 + prevc2 = c2 + x += dx + y += dy + rdes.buildModel() + +def makeCylModel(): + segLen = dendLength / numDendSeg + rdes = rd.rdesigneur( + stealCellFromLibrary = True, + verbose = False, + # cellProto syntax: ['ballAndStick', 'name', somaDia, somaLength, dendDia, dendLength, numDendSegments ] + # The numerical arguments are all optional + cellProto = + [['ballAndStick', 'soma', dendDia, segLen, dendDia, 2*dendLength, 2*numDendSeg]], + passiveDistrib = [[ '#', 'RM', str(dendRM), 'CM', str(dendCM), 'RA', str(dendRA) ]], + stimList = [['soma', '1', '.', 'inject', stimStr ]], + ) + rdes.buildModel() + +def main(): + global vec + makeDisplay() + quit() + +def updateRM(RM_): + global RM + RM = RM_ + updateDisplay() + +def updateCM(CM_): + global CM + CM = CM_ + updateDisplay() + +def updateRA(RA_): + global RA + RA = RA_ + updateDisplay() + +def updateLen(val): # This does the length of the entire cell. + global length + length = val * 0.001 + updateDisplay() + +def updateDia(val): + global dia + dia = val * 1e-6 + updateDisplay() + +class stimToggle(): + def __init__( self, toggle, ax ): + self.duration = 1 + self.toggle = toggle + self.ax = ax + + def click( self, event ): + global stimStr + if self.duration < 0.5: + self.duration = 1.0 + self.toggle.label.set_text( "Long Stim" ) + self.toggle.color = "yellow" + self.toggle.hovercolor = "yellow" + stimStr = "2e-10" + else: + self.duration = 0.001 + self.toggle.label.set_text( "Short Stim" ) + self.toggle.color = "orange" + self.toggle.hovercolor = "orange" + stimStr = "40e-9*(t<0.001)-36e-9*(t>0.001&&t<0.002)" + updateDisplay() + #self.ax.update() + #self.ax.redraw_in_frame() + #self.ax.draw() + +def printSomaVm(): + print("This is somaVm" ) + +def updateDisplay(): + makeCylModel() + moose.element( '/model/elec/' ).name = 'Cyl' + moose.element( '/model' ).name = 'model1' + makeYmodel() + moose.element( '/model/elec/' ).name = 'Y' + moose.move( '/model1/Cyl', '/model' ) + #moose.le( '/model/Y' ) + #print "################################################" + #moose.le( '/model/Cyl' ) + vecYdend = moose.wildcardFind( '/model/Y/soma,/model/Y/dend#' ) + vecYbranch1 = moose.wildcardFind( '/model/Y/branch1#' ) + vecYbranch2 = moose.wildcardFind( '/model/Y/branch2#' ) + vecCyl = moose.wildcardFind( '/model/Cyl/#[ISA=CompartmentBase]' ) + #vec[0].inject = 1e-10 + moose.reinit() + dt = interval1 + for i in lines: + moose.start( dt ) + #print( len(vecCyl), len(vecYdend), len(vecYbranch1), len(vecYbranch2) ) + i.CylLines.set_ydata( [v.Vm*1000 for v in vecCyl] ) + i.YdendLines.set_ydata( [v.Vm*1000 for v in vecYdend] ) + i.Ybranch1Lines.set_ydata( [v.Vm*1000 for v in vecYbranch1] ) + i.Ybranch2Lines.set_ydata( [v.Vm*1000 for v in vecYbranch2] ) + dt = interval2 + + moose.delete( '/model' ) + moose.delete( '/model1' ) + moose.delete( '/library' ) + # Put in something here for the time-series on soma + +def doQuit( event ): + cylLength = 2*dendLength*float(2*numDendSeg+1)/(2*numDendSeg) + print( "The cylinder parameters were:\n" + "Length = {} microns\n" + "Diameter = {} microns\n" + "RM = {} Ohms.m^2\n" + "RA = {} Ohms.m\n" + "CM = {} Farads/m^2\n" + "Your branch parameters were:\n" + "Length = {:.2f} microns\n" + "Diameter = {:.2f} microns\n" + "RM = {:.2f} Ohms.m^2\n" + "RA = {:.2f} Ohms.m\n" + "CM = {:.3f} Farads/m^2\n".format( + cylLength*1e6, dendDia*1e6, dendRM, dendRA, dendCM, + length*1e6, dia*1e6, RM, RA, CM ) ) + print( "The branch point was at 1100 microns from the left" ) + + quit() + +def makeDisplay(): + global lines + #img = mpimg.imread( 'CableEquivCkt.png' ) + img = mpimg.imread( 'RallsLaw.png' ) + #plt.ion() + fig = plt.figure( figsize=(10,12) ) + png = fig.add_subplot(311) + imgplot = plt.imshow( img ) + plt.axis('off') + ax2 = fig.add_subplot(312) + #ax.set_ylim( 0, 0.1 ) + plt.ylabel( 'Vm (mV)' ) + plt.ylim( -70, 0.0 ) + plt.xlabel( 'Position (microns)' ) + #ax2.autoscale( enable = True, axis = 'y' ) + plt.title( "Membrane potential as a function of position along cell." ) + #for i,col in zip( range( 5 ), ['k', 'b', 'g', 'y', 'm' ] ): + for i,col in zip( range( 2 ), ['b', 'k' ] ): + lw = lineWrapper() + lw.YdendLines, = ax2.plot( np.arange(0, numDendSeg+1, 1 ), + np.zeros(numDendSeg+1), col + '.' ) + lw.Ybranch1Lines, = ax2.plot( np.arange(0, numDendSeg, 1) + numDendSeg + 1, + np.zeros(numDendSeg), col + ':' ) + lw.Ybranch2Lines, = ax2.plot( np.arange(0, numDendSeg, 1) + numDendSeg + 1, + np.zeros(numDendSeg) + numDendSeg + 1, col + '.' ) + lw.CylLines, = ax2.plot( np.arange(0, numDendSeg*2+1, 1), + np.zeros(numDendSeg*2+1), 'r-' ) + lines.append( lw ) + + ax = fig.add_subplot(313) + plt.axis('off') + axcolor = 'palegreen' + axStim = plt.axes( [0.02,0.05, 0.20,0.03], facecolor='green' ) + axReset = plt.axes( [0.25,0.05, 0.30,0.03], facecolor='blue' ) + axQuit = plt.axes( [0.60,0.05, 0.30,0.03], facecolor='blue' ) + axRM = plt.axes( [0.25,0.1, 0.65,0.03], facecolor=axcolor ) + axCM = plt.axes( [0.25,0.15, 0.65,0.03], facecolor=axcolor ) + axRA = plt.axes( [0.25,0.20, 0.65,0.03], facecolor=axcolor ) + axLen = plt.axes( [0.25,0.25, 0.65,0.03], facecolor=axcolor ) + axDia = plt.axes( [0.25,0.30, 0.65,0.03], facecolor=axcolor ) + #aInit = Slider( axAinit, 'A init conc', 0, 10, valinit=1.0, valstep=0.2) + stim = Button( axStim, 'Long Stim', color = 'yellow' ) + stimObj = stimToggle( stim, axStim ) + + reset = Button( axReset, 'Reset', color = 'cyan' ) + q = Button( axQuit, 'Quit', color = 'pink' ) + RM = Slider( axRM, 'RM ( ohm.m^2 )', 0.1, 10, valinit=1.0 ) + CM = Slider( axCM, 'CM ( Farad/m^2)', 0.001, 0.05, valinit=0.01, valfmt = '%0.3f' ) + RA = Slider( axRA, 'RA ( ohm.m', 0.1, 10, valinit=1.0 ) + length = Slider( axLen, 'Length of branches (mm)', 0.1, 10, valinit=2.0 ) + dia = Slider( axDia, 'Diameter of branches (um)', 0.1, 10, valinit=1.0 ) + def resetParms( event ): + RM.reset() + CM.reset() + RA.reset() + length.reset() + dia.reset() + + + stim.on_clicked( stimObj.click ) + reset.on_clicked( resetParms ) + q.on_clicked( doQuit ) + RM.on_changed( updateRM ) + CM.on_changed( updateCM ) + RA.on_changed( updateRA ) + length.on_changed( updateLen ) + dia.on_changed( updateDia ) + + updateDisplay() + + plt.show() + +# Run the 'main' if this script is executed standalone. +if __name__ == '__main__': + main() diff --git a/moose-examples/tutorials/Rdesigneur/README.txt b/moose-examples/tutorials/Rdesigneur/README.txt index b64d89a987600062e60963a0484823aa31a8f3a6..e76845689429a3080d8b04131c3ad7798ddf29b3 100644 --- a/moose-examples/tutorials/Rdesigneur/README.txt +++ b/moose-examples/tutorials/Rdesigneur/README.txt @@ -291,6 +291,16 @@ ex9.2_spines_in_neuronal_morpho.py: Add spines to a neuron built from a - See if you can deliver the current injection to the spine. Hint: the name of the spine compartments is 'head#' where # is the index of the spine. + + +ex9.3_spiral_spines.py: Just for fun. Illustrates how to place spines in a +spiral around the dendrite. For good measure the spines get bigger the further +they are from the soma. Note that the uniform spacing of spines is signified +by the negative minSpacing term, the fourth argument to spineDistrib. + Suggestions: + - Play with expressions for spine size and angular placement. + - See what happens if the segment size gets smaller than the + spine spacing. To come: rdes_ex10.py: Build a spiny neuron, and insert the oscillatory chemical model diff --git a/moose-examples/tutorials/Rdesigneur/ex3.2_squid_axon_propgn.py b/moose-examples/tutorials/Rdesigneur/ex3.2_squid_axon_propgn.py index 9729a0d653942428734e7966205fe06f789ff876..cb3e0a5587b67c1c9abd78eda16ab478ee41e1d6 100644 --- a/moose-examples/tutorials/Rdesigneur/ex3.2_squid_axon_propgn.py +++ b/moose-examples/tutorials/Rdesigneur/ex3.2_squid_axon_propgn.py @@ -43,7 +43,7 @@ rdes = rd.rdesigneur( chanDistrib = [ ['Na', '#', 'Gbar', '1200' ], ['K', '#', 'Gbar', '360' ]], - stimList = [['soma', '1', '.', 'inject', '(t>0.01 && t<0.2) * 2e-11' ]], + stimList = [['soma', '1', '.', 'inject', '(t>0.005 && t<0.2) * 2e-11' ]], plotList = [['soma', '1', '.', 'Vm', 'Membrane potential']], moogList = [['#', '1', '.', 'Vm', 'Vm (mV)']] ) @@ -51,4 +51,4 @@ rdes = rd.rdesigneur( rdes.buildModel() moose.reinit() -rdes.displayMoogli( 0.00005, 0.05, 0.0 ) +rdes.displayMoogli( 0.00005, 0.04, 0.0 ) diff --git a/moose-examples/tutorials/Rdesigneur/ex3.3_AP_collision.py b/moose-examples/tutorials/Rdesigneur/ex3.3_AP_collision.py index b6a8ce2a80a0ad0435703eb4159f646f43e36ae8..dfe4e8cda578cf12863d7d04a2419c06c7e04b8b 100644 --- a/moose-examples/tutorials/Rdesigneur/ex3.3_AP_collision.py +++ b/moose-examples/tutorials/Rdesigneur/ex3.3_AP_collision.py @@ -52,4 +52,4 @@ rdes = rd.rdesigneur( rdes.buildModel() moose.reinit() -rdes.displayMoogli( 0.00005, 0.05, 0.0 ) +rdes.displayMoogli( 0.00005, 0.03, 0.0 ) diff --git a/moose-examples/tutorials/Rdesigneur/ex3.4_myelinated_axon.py b/moose-examples/tutorials/Rdesigneur/ex3.4_myelinated_axon.py index e91f7898249c78688016dcb98c0e547b7101b430..c609529d0eed4ced43c5ca76345860818e1df40a 100644 --- a/moose-examples/tutorials/Rdesigneur/ex3.4_myelinated_axon.py +++ b/moose-examples/tutorials/Rdesigneur/ex3.4_myelinated_axon.py @@ -58,12 +58,6 @@ rdes = rd.rdesigneur( moogList = [['#', '1', '.', 'Vm', 'Vm (mV)']] ) - rdes.buildModel() - -for i in moose.wildcardFind( "/model/elec/#/Na" ): - print(i.parent.name, i.Gbar) - moose.reinit() - rdes.displayMoogli( 0.00005, 0.05, 0.0 ) diff --git a/moose-examples/tutorials/Rdesigneur/ex7.2_CICR.py b/moose-examples/tutorials/Rdesigneur/ex7.2_CICR.py index a44e4a1a7833327b788fd7e98dc730eb89357fdb..38447a8d0a2c040874cc6c8f1b91e88481a1c1fb 100644 --- a/moose-examples/tutorials/Rdesigneur/ex7.2_CICR.py +++ b/moose-examples/tutorials/Rdesigneur/ex7.2_CICR.py @@ -15,6 +15,7 @@ rdes = rd.rdesigneur( turnOffElec = True, chemDt = 0.005, chemPlotDt = 0.02, + numWaveFrames = 200, diffusionLength = 1e-6, useGssa = False, addSomaChemCompt = False, diff --git a/moose-examples/tutorials/Rdesigneur/ex9.3_spiral_spines.py b/moose-examples/tutorials/Rdesigneur/ex9.3_spiral_spines.py new file mode 100644 index 0000000000000000000000000000000000000000..fe5c39c544f28a24c70025c2a8ad63bb2959edce --- /dev/null +++ b/moose-examples/tutorials/Rdesigneur/ex9.3_spiral_spines.py @@ -0,0 +1,19 @@ +########################################################################## +# This illustrates some of the capabilities for spine placement. +# It has spines whose size increase with distance from the soma. +# Further, the angular direction of the spines spirals around the dendrite. +########################################################################## +import moose +import rdesigneur as rd +rdes = rd.rdesigneur( + cellProto = [['ballAndStick', 'elec', 10e-6, 10e-6, 2e-6, 300e-6, 50]], + spineProto = [['makePassiveSpine()', 'spine']], + spineDistrib = [['spine', '#dend#', '3e-6', '-1e-6', '1+p*2e4', '0', 'p*6.28e7', '0']], + stimList = [['soma', '1', '.', 'inject', '(t>0.02) * 1e-9' ]], + moogList = [['#', '1', '.', 'Vm', 'Soma potential']] +) + +rdes.buildModel() + +moose.reinit() +rdes.displayMoogli( 0.0002, 0.025, 0.02 ) diff --git a/moose-gui/objectedit.py b/moose-gui/objectedit.py index cbc8c22bffb77b358b864c094e490aea7372c9d6..d5d66b60c96fa506af50e620260a109f5deb8e29 100644 --- a/moose-gui/objectedit.py +++ b/moose-gui/objectedit.py @@ -175,10 +175,14 @@ class ObjectEditModel(QtCore.QAbstractTableModel): self.fields.append(fieldName) #harsha: For signalling models will be pulling out notes field from Annotator # can updates if exist for other types also - if ( isinstance(self.mooseObject, moose.PoolBase) - or isinstance(self.mooseObject,moose.EnzBase) - or isinstance(self.mooseObject,moose.Neutral)) : - self.fields.append("Color") + if (isinstance (self.mooseObject,moose.ChemCompt) or \ + isinstance(self.mooseObject,moose.ReacBase) or \ + isinstance(moose.element(moose.element(self.mooseObject).parent),moose.EnzBase) \ + ): + pass + else: + self.fields.append("Color") + flag = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable self.fieldFlags[fieldName] = flag diff --git a/moose-gui/plugins/kkit.py b/moose-gui/plugins/kkit.py index 4870a71d167ec86b8370bf493a81a717a17493d2..3cd531bb4498af9fc86fc75bc11a9a4376f602d0 100644 --- a/moose-gui/plugins/kkit.py +++ b/moose-gui/plugins/kkit.py @@ -6,13 +6,14 @@ __version__ = "1.0.0" __maintainer__ = "HarshaRani" __email__ = "hrani@ncbs.res.in" __status__ = "Development" -__updated__ = "Sep 7 2018" +__updated__ = "Sep 11 2018" #Change log: # 2018 -#Jun 18: update the color of the group from objecteditor +#sep 11: comparment size is calculated based on group sceneBoundingRect size #Sep 07: in positionChange all the group's boundingRect is calculated # and when group is moved the children's position are stored +#Jun 18: update the color of the group from objecteditor #code import math @@ -908,7 +909,16 @@ class KineticsWidget(EditorWidgetBase): # rectcompt = calculateChildBoundingRect(grpcompt) rectgrp = calculateChildBoundingRect(v) v.setRect(rectgrp.x()-10,rectgrp.y()-10,(rectgrp.width()+20),(rectgrp.height()+20)) - + for k, v in self.qGraCompt.items(): + #rectcompt = v.childrenBoundingRect() + rectcompt = calculateChildBoundingRect(v) + comptBoundingRect = v.boundingRect() + if not comptBoundingRect.contains(rectcompt): + self.updateCompartmentSize(v) + + else: + rectcompt = calculateChildBoundingRect(v) + v.setRect(rectcompt.x()-10,rectcompt.y()-10,(rectcompt.width()+20),(rectcompt.height()+20)) else: mobj = self.mooseId_GObj[element(mooseObject)] self.updateArrow(mobj) @@ -938,11 +948,11 @@ class KineticsWidget(EditorWidgetBase): comptBoundingRect = v.boundingRect() if not comptBoundingRect.contains(rectcompt): self.updateCompartmentSize(v) + else: rectcompt = calculateChildBoundingRect(v) v.setRect(rectcompt.x()-10,rectcompt.y()-10,(rectcompt.width()+20),(rectcompt.height()+20)) - pass - + def updateGrpSize(self,grp): compartmentBoundary = grp.rect() diff --git a/moose-gui/plugins/kkitUtil.py b/moose-gui/plugins/kkitUtil.py index 7185988005fc7c1c15e2795e878870bd1fd9b73f..b7dde1b7cc061f53c9075db806ac4fce9ba32117 100644 --- a/moose-gui/plugins/kkitUtil.py +++ b/moose-gui/plugins/kkitUtil.py @@ -5,12 +5,16 @@ __version__ = "1.0.0" __maintainer__ = "HarshaRani" __email__ = "hrani@ncbs.res.in" __status__ = "Development" -__updated__ = "Oct 18 2017" +__updated__ = "Sep 17 2018" ''' +2018 +Sep 17: when vertical or horizontal layout is applied for group, compartment size is recalculated +Sep 11: group size is calculated based on sceneBoundingRect for compartment size +2017 Oct 18 some of the function moved to this file from kkitOrdinateUtils ''' -from moose import Annotator,element +from moose import Annotator,element,ChemCompt from kkitQGraphics import PoolItem, ReacItem,EnzItem,CplxItem,GRPItem,ComptItem from PyQt4 import QtCore,QtGui,QtSvg from PyQt4.QtGui import QColor @@ -127,9 +131,10 @@ def moveX(reference, collider, layoutPt, margin): layoutPt.drawLine_arrow(itemignoreZooming=False) def handleCollisions(compartments, moveCallback, layoutPt,margin = 5.0): - + print " handelCollision" if len(compartments) is 0 : return compartments = sorted(compartments, key = lambda c: c.sceneBoundingRect().center().x()) + print " compartment ",compartments reference = compartments.pop(0); print (reference.name) referenceRect = reference.sceneBoundingRect() @@ -138,6 +143,18 @@ def handleCollisions(compartments, moveCallback, layoutPt,margin = 5.0): ) for collider in colliders: moveCallback(reference, collider, layoutPt,margin) + #print (reference.mobj).parent + if isinstance(element(((reference.mobj).parent)),ChemCompt): + v = layoutPt.qGraCompt[element(((reference.mobj).parent))] + #layoutPt.updateCompartmentSize(x) + rectcompt = calculateChildBoundingRect(v) + comptBoundingRect = v.boundingRect() + if not comptBoundingRect.contains(rectcompt): + layoutPt.updateCompartmentSize(v) + + else: + rectcompt = calculateChildBoundingRect(v) + v.setRect(rectcompt.x()-10,rectcompt.y()-10,(rectcompt.width()+20),(rectcompt.height()+20)) return handleCollisions(compartments, moveCallback, layoutPt,margin) def calculateChildBoundingRect(compt): @@ -149,7 +166,6 @@ def calculateChildBoundingRect(compt): ypos = [] xpos = [] for l in compt.childItems(): - ''' All the children including pool,reac,enz,polygon(arrow),table ''' if not isinstance(l,QtSvg.QGraphicsSvgItem): if (not isinstance(l,QtGui.QGraphicsPolygonItem)): @@ -158,11 +174,18 @@ def calculateChildBoundingRect(compt): xpos.append(l.pos().x()) ypos.append(l.pos().y()+l.boundingRect().bottomRight().y()) ypos.append(l.pos().y()) + else: + xpos.append(l.sceneBoundingRect().x()) + xpos.append(l.sceneBoundingRect().bottomRight().x()) + ypos.append(l.sceneBoundingRect().y()) + ypos.append(l.sceneBoundingRect().bottomRight().y()) + ''' xpos.append(l.rect().x()) xpos.append(l.boundingRect().bottomRight().x()) ypos.append(l.rect().y()) ypos.append(l.boundingRect().bottomRight().y()) + ''' if (isinstance(l,PoolItem) or isinstance(l,EnzItem)): ''' For Enz cplx height and for pool function height needs to be taken''' for ll in l.childItems(): diff --git a/moose-gui/plugins/kkitViewcontrol.py b/moose-gui/plugins/kkitViewcontrol.py index a813bffe41e544d21228295f1b2328f260d18af0..06fa8b7f9cca58f97379d0af03360736abe6e311 100644 --- a/moose-gui/plugins/kkitViewcontrol.py +++ b/moose-gui/plugins/kkitViewcontrol.py @@ -144,13 +144,14 @@ class GraphicalView(QtGui.QGraphicsView): elif gsolution[1] == GROUP_INTERIOR: groupInteriorfound = True groupList.append(gsolution) - if item.name == COMPARTMENT: csolution = (item, self.resolveCompartmentInteriorAndBoundary(item, position)) if csolution[1] == COMPARTMENT_BOUNDARY: - comptInteriorfound = True - comptBoundary.append(csolution) - + return csolution + # elif csolution[1] == COMPARTMENT_INTERIOR: + # comptInteriorfound = True + # comptBoundary.append(csolution) + if groupInteriorfound: if comptInteriorfound: return comptBoundary[0] @@ -181,7 +182,6 @@ class GraphicalView(QtGui.QGraphicsView): ##This is kept for reference, so that if object (P,R,E,Tab,Fun) is moved outside the compartment, #then it need to be pull back to original position self.state["press"]["scenepos"] = item.parent().scenePos() - if itemType == COMPARTMENT_INTERIOR or itemType == GROUP_BOUNDARY or itemType == GROUP_INTERIOR: self.removeConnector() @@ -194,6 +194,9 @@ class GraphicalView(QtGui.QGraphicsView): if itemType == GROUP_BOUNDARY: popupmenu = QtGui.QMenu('PopupMenu', self) popupmenu.addAction("DeleteGroup", lambda : self.deleteGroup(item,self.layoutPt)) + popupmenu.addAction("LinearLayout", lambda : handleCollisions(list(self.layoutPt.qGraGrp.values()), moveX, self.layoutPt)) + popupmenu.addAction("VerticalLayout" ,lambda : handleCollisions(list(self.layoutPt.qGraGrp.values()), moveMin, self.layoutPt )) + #popupmenu.addAction("CloneGroup" ,lambda : handleCollisions(comptList, moveMin, self.layoutPt )) popupmenu.exec_(self.mapToGlobal(event.pos())) @@ -361,6 +364,12 @@ class GraphicalView(QtGui.QGraphicsView): grpCmpt = self.findGraphic_groupcompt(item) if movedGraphObj.parentItem() != grpCmpt: '''Not same compartment/group to which it belonged to ''' + if isinstance(movedGraphObj,FuncItem): + funcPool = moose.element((movedGraphObj.mobj.neighbors['valueOut'])[0]) + parentGrapItem = self.layoutPt.mooseId_GObj[moose.element(funcPool)] + if parentGrapItem.parentItem() != grpCmpt: + self.objectpullback("Functionparent",grpCmpt,movedGraphObj,xx,yy) + if isinstance(movedGraphObj,(EnzItem,MMEnzItem)): parentPool = moose.element((movedGraphObj.mobj.neighbors['enzDest'])[0]) if isinstance(parentPool,PoolBase): @@ -558,16 +567,21 @@ class GraphicalView(QtGui.QGraphicsView): if messgtype.lower() == "all": messgstr = "The object name \'{0}\' exist in \'{1}\' {2}".format(movedGraphObj.mobj.name,item.mobj.name,desObj) elif messgtype.lower() =="enzymeparent": - messgstr = "The Enzyme parent \'{0}\' doesn't exist in \'{2}\' {1} \n If you need to move the enzyme to {1} first parent pool needs to be moved".format(movedGraphObj.mobj.parent.name,desObj,item.mobj.name) + messgstr = "The Enzyme parent \'{0}\' doesn't exist in \'{2}\' {1} \n If you need to move the enzyme to {1} first move the parent pool".format(movedGraphObj.mobj.parent.name,desObj,item.mobj.name) elif messgtype.lower() == "enzyme": messgstr = "The Enzyme \'{0}\' already exist in \'{2}\' {1}".format(movedGraphObj.mobj.name,desObj,item.mobj.name) elif messgtype.lower() == "empty": messgstr = "The object can't be moved to empty space" + elif messgtype.lower() =="functionparent": + messgstr = "The Function parent \'{0}\' doesn't exist in \'{2}\' {1} \n If you need to move the function to {1} first move the parent pool".format(movedGraphObj.mobj.parent.name,desObj,item.mobj.name) + QtGui.QMessageBox.warning(None,'Could not move the object', messgstr ) QtGui.QApplication.setOverrideCursor(QtGui.QCursor(Qt.Qt.ArrowCursor)) def moveObjSceneParent(self,item,movedGraphObj,itempos,eventpos): ''' Scene parent object needs to be updated ''' + if isinstance(movedGraphObj,FuncItem): + return prevPar = movedGraphObj.parentItem() movedGraphObj.setParentItem(item)