Commit 11695093 authored by David Saxton's avatar David Saxton

KmPlot can now draw explicit differential equations :) (e.g. f''(x) = -f(x)).

svn path=/trunk/KDE/kdeedu/kmplot/; revision=556871
parent 7f9f6f47
......@@ -147,3 +147,8 @@ kmplot/view.h
kmplot/maindlg.cpp
kmplot/maindlg.h
kmplot/equationeditor.ui
kmplot/vector.cpp
kmplot/vector.h
kmplot/vector.cpp
kmplot/vector.h
kmplot/initialconditionswidget.ui
......@@ -25,6 +25,7 @@ set(kmplotpart_PART_SRCS
parameterswidget.cpp
coordsconfigdialog.cpp
parameteranimator.cpp
vector.cpp
)
kde4_automoc(${kmplotpart_PART_SRCS})
......@@ -44,6 +45,7 @@ kde4_add_ui_files(kmplotpart_PART_SRCS
parameterswidget.ui
parameteranimator.ui
equationeditor.ui
initialconditionswidget.ui
)
kde4_add_kcfg_files(kmplotpart_PART_SRCS settings.kcfgc )
......
......@@ -123,6 +123,13 @@ EquationEdit::EquationEdit( QWidget * parent )
}
void EquationEdit::setEquationType( Equation::Type type )
{
delete m_equation;
m_equation = new Equation( type, 0 );
}
void EquationEdit::showEditButton( bool show )
{
m_editButton->setVisible( show );
......@@ -179,7 +186,7 @@ void EquationEdit::checkTextValidity( )
ok = XParser::self()->errorString().isEmpty();
}
kDebug() << k_funcinfo << "ok="<<ok<<" XParser::self()->errorPosition()="<<XParser::self()->errorPosition()<<endl;
// kDebug() << k_funcinfo << "ok="<<ok<<" XParser::self()->errorPosition()="<<XParser::self()->errorPosition()<<endl;
if ( ok )
setError( QString(), -1 );
......
......@@ -25,6 +25,8 @@
#ifndef EQUATIONEDIT_H
#define EQUATIONEDIT_H
#include "function.h"
#include <kdialog.h>
#include <QSyntaxHighlighter>
......@@ -129,6 +131,10 @@ class EquationEdit : public QWidget
* Hide/show the edit button.
*/
void showEditButton( bool show );
/**
* Changes the equation type.
*/
void setEquationType( Equation::Type type );
QString text() const { return m_equationEditWidget->toPlainText(); }
void clear() { m_equationEditWidget->clear(); }
......
......@@ -30,6 +30,7 @@
#include "xparser.h"
#include <kdebug.h>
#include <QMap>
#include <assert.h>
#include <cmath>
......@@ -58,7 +59,7 @@ bool Value::updateExpression( const QString & expression )
}
bool Value::operator == ( const Value & other )
bool Value::operator == ( const Value & other ) const
{
return m_expression == other.expression();
}
......@@ -154,6 +155,43 @@ Qt::PenStyle PlotAppearance::stringToPenStyle( const QString & style )
//BEGIN class DifferentialState
DifferentialState::DifferentialState()
{
x = 0;
}
DifferentialState::DifferentialState( int order )
{
x = 0;
setOrder( order );
}
void DifferentialState::setOrder( int order )
{
y.resize( order );
y0.resize( order );
resetToInitial();
}
void DifferentialState::resetToInitial()
{
x = x0.value();
y = y0;
}
bool DifferentialState::operator == ( const DifferentialState & other ) const
{
return (x0 == other.x0) && (x == other.x) && (y0 == other.y0) && (y == other.y);
}
//END class DifferentialState
//BEGIN class Equation
Equation::Equation( Type type, Function * parent )
: m_type( type ),
......@@ -161,6 +199,9 @@ Equation::Equation( Type type, Function * parent )
{
mem = new unsigned char [MEMSIZE];
mptr = 0;
// if ( type == Differential )
// addDifferentialState();
}
......@@ -171,7 +212,13 @@ Equation::~ Equation()
}
QString Equation::name( ) const
int Equation::order( ) const
{
return name(false).count( '\'' );
}
QString Equation::name( bool removePrimes ) const
{
if ( m_fstr.isEmpty() )
return QString();
......@@ -181,7 +228,12 @@ QString Equation::name( ) const
if ( pos == -1 )
return QString();
return m_fstr.left( pos );
QString n = m_fstr.left( pos ).trimmed();
if ( removePrimes )
n.remove( '\'' );
return n;
}
......@@ -192,26 +244,54 @@ QStringList Equation::parameters( ) const
if ( (p1 == -1) || (p2 == -1) )
return QStringList();
QString parameters = m_fstr.mid( p1+1, p2-p1-1 );
return parameters.split( ',' );
QStringList parameters = m_fstr.mid( p1+1, p2-p1-1 ).split( ',' );
// If we are a differential equation, then add on y, y', etc
if ( type() == Differential )
{
QString n = name();
int order = this->order();
for ( int i = 0; i < order; ++i )
{
parameters << n;
n += '\'';
}
}
return parameters;
}
bool Equation::setFstr( const QString & fstr )
{
// kDebug() << k_funcinfo << "fstr: "<<fstr<<endl;
kDebug() << k_funcinfo << "fstr: "<<fstr<<" ( type() == Differential )="<<( type() == Differential )<<endl;
QString prevFstr = m_fstr;
m_fstr = fstr;
// require order to be greater than 0 for differential equations
if ( (type() == Differential) && (order() < 1) )
{
m_fstr = prevFstr;
kDebug() << "Zero order!\n";
return false;
}
XParser::self()->initEquation( this );
if ( XParser::self()->parserError( false ) != Parser::ParseSuccess )
{
kDebug() << k_funcinfo << "BAD XParser::self()->errorPosition()="<< XParser::self()->errorPosition()<< " error="<<XParser::self()->errorString()<< endl;
m_fstr = prevFstr;
XParser::self()->initEquation( this );
// kDebug() << k_funcinfo << "BAD XParser::self()->errorPosition()="<< XParser::self()->errorPosition()<< " error="<<XParser::self()->errorString()<< endl;
return false;
}
// If we are a differential equation, then update the order. and reset the other stuff
foreach ( DifferentialState state, differentialStates )
state.setOrder( order() );
resetLastIntegralPoint();
return true;
}
......@@ -232,11 +312,19 @@ void Equation::setIntegralStart( const Value & x, const Value & y )
}
DifferentialState * Equation::addDifferentialState( )
{
differentialStates << DifferentialState( order() );
return & differentialStates[ differentialStates.size() - 1 ];
}
bool Equation::operator !=( const Equation & other )
{
return (fstr() != other.fstr()) ||
(integralInitialX() != other.integralInitialX()) ||
(integralInitialY() != other.integralInitialY());
(integralInitialY() != other.integralInitialY()) ||
(differentialStates != other.differentialStates);
}
......@@ -244,6 +332,7 @@ Equation & Equation::operator =( const Equation & other )
{
setFstr( other.fstr() );
setIntegralStart( other.integralInitialX(), other.integralInitialY() );
differentialStates = other.differentialStates;
return * this;
}
......@@ -476,33 +565,33 @@ QList< Plot > Function::allPlots( ) const
{
QList< Plot > list;
for ( PMode p = Derivative0; p <= Integral; p = PMode(p+1) )
Plot plot;
plot.setFunctionID( id );
plot.plotNumberCount = m_parameters.useList ? m_parameters.list.size() + (m_parameters.useSlider?1:0) : 1;
bool singlePlot = (!m_parameters.useList && !m_parameters.useSlider) || m_parameters.animating;
if ( singlePlot )
{
int i = 0;
if ( !plotAppearance( p ).visible )
continue;
Plot plot;
plot.setFunctionID( id );
plot.plotMode = p;
plot.plotNumberCount = m_parameters.useList ? m_parameters.list.size() + (m_parameters.useSlider?1:0) : 1;
bool usedParameter = false;
if ( m_parameters.animating )
plot.parameter = Parameter( Parameter::Animated );
// Don't use slider or list parameters if animating
list << plot;
}
else
{
int i = 0;
if ( !m_parameters.animating && m_parameters.useSlider )
if ( m_parameters.useSlider )
{
Parameter param( Parameter::Slider );
param.setSliderID( m_parameters.sliderID );
plot.parameter = param;
plot.plotNumber = i++;
list << plot;
usedParameter = true;
}
if ( !m_parameters.animating && m_parameters.useList )
if ( m_parameters.useList )
{
int pos = 0;
foreach ( Value v, m_parameters.list )
......@@ -512,21 +601,42 @@ QList< Plot > Function::allPlots( ) const
plot.parameter = param;
plot.plotNumber = i++;
list << plot;
usedParameter = true;
}
}
if ( m_parameters.animating )
}
// Copy each plot in the list for other variations
QList< Plot > duplicated;
if ( type() == Cartesian )
{
for ( PMode p = Derivative0; p <= Integral; p = PMode(p+1) )
{
Parameter param( Parameter::Animated );
plot.parameter = param;
foreach ( Plot plot, list )
{
if ( !plotAppearance(p).visible )
continue;
plot.plotMode = p;
duplicated << plot;
}
}
if ( !usedParameter )
list << plot;
}
else if ( type() == Differential )
{
for ( int i = 0; i < eq[0]->differentialStates.size(); ++i )
{
foreach ( Plot plot, list )
{
plot.state = i;
duplicated << plot;
}
}
}
else
duplicated = list;
return list;
return duplicated;
}
//END class Function
......@@ -575,6 +685,7 @@ bool Parameter::operator == ( const Parameter & other ) const
//BEGIN class Plot
Plot::Plot( )
{
state = -1;
plotNumberCount = 1;
plotNumber = 0;
m_function = 0;
......@@ -587,7 +698,8 @@ bool Plot::operator ==( const Plot & other ) const
{
return ( m_functionID == other.functionID() ) &&
( plotMode == other.plotMode ) &&
( parameter == other.parameter );
( parameter == other.parameter ) &&
( state == other.state );
}
......
......@@ -26,12 +26,13 @@
#ifndef FUNCTION_H
#define FUNCTION_H
#include "vector.h"
#include <QColor>
#include <QPointF>
#include <QString>
#include <QVector>
class Equation;
class Function;
class Plot;
......@@ -64,11 +65,11 @@ class Value
* This checks if the expression strings (and hence values) are
* identical.
*/
bool operator == ( const Value & other );
bool operator == ( const Value & other ) const;
/**
* Checks for inequality.
*/
bool operator != ( const Value & other ) { return !((*this) == other); }
bool operator != ( const Value & other ) const { return !((*this) == other); }
protected:
QString m_expression;
......@@ -108,6 +109,38 @@ class PlotAppearance
};
/**
* Used in differential equations; contains the initial conditions and the
* currently calculated value (used as a cache).
*/
class DifferentialState
{
public:
DifferentialState();
DifferentialState( int order );
/**
* Resizes y, y0. Also calls resetToInitial.
*/
void setOrder( int order );
/**
* Sets y=y0, x=x0.
*/
void resetToInitial();
Value x0; ///< the initial x-value
QVector<Value> y0; ///< the value of ( f, f', f'', ...., f^(n) ) at x0
double x; ///< the current x value
Vector y; ///< the value of ( f, f', f'', ...., f^(n) ) at x
/**
* Whether the initial conditions and current state are the same.
*/
bool operator == ( const DifferentialState & other ) const;
};
typedef QVector<DifferentialState> DifferentialStates;
/**
* This is the non-visual mathematical expression.
*/
......@@ -154,7 +187,7 @@ class Equation
* @return the name of the function, e.g. for the cartesian function
* f(x)=x^2, this would return "f".
*/
QString name() const;
QString name( bool removePrimes = true ) const;
/**
* \return a list of parameters, e.g. {x} for "f(x)=y", and {x,y,k} for
* "f(x,y,k)=(x+k)(y+k)".
......@@ -187,9 +220,22 @@ class Equation
* Resets lastIntegralPoint to the initial integral point.
*/
void resetLastIntegralPoint();
/**
* \return the order of the differential equations.
*/
int order() const;
QPointF lastIntegralPoint; ///< needed for numeric integration
/// For differential equations, all the states
DifferentialStates differentialStates;
/**
* Adds an initial condition (for differential equations).
* \return a pointer to the state
*/
DifferentialState * addDifferentialState();
protected:
/// \note when adding new member variables, make sure to update operator != and operator =
const Type m_type;
......@@ -443,6 +489,10 @@ class Plot
* this plot is for.
*/
void updateFunctionParameter() const;
/**
* For differential equations, which state to draw.
*/
int state;
protected:
void updateCached();
......
......@@ -97,6 +97,7 @@ FunctionEditor::FunctionEditor( KMenu * createNewPlotsMenu, QWidget * parent )
m_editor->parametricY->setInputType( EquationEdit::Function );
m_editor->implicitEquation->setInputType( EquationEdit::Function );
m_editor->differentialEquation->setInputType( EquationEdit::Function );
m_editor->differentialEquation->setEquationType( Equation::Differential );
for ( unsigned i = 0; i < 5; ++i )
m_editor->stackedWidget->widget(i)->layout()->setMargin( 0 );
......@@ -459,7 +460,9 @@ void FunctionEditor::initFromDifferential()
m_editor->differential_f0->init( f->plotAppearance( Function::Derivative0 ) );
m_editor->differentialParameters->init( f->m_parameters );
m_editor->initialConditions->init( f );
m_editor->differentialTabWidget->setCurrentIndex( 0 );
m_editor->stackedWidget->setCurrentIndex( 4 );
m_editor->differentialEquation->setFocus();
}
......@@ -472,8 +475,8 @@ void FunctionEditor::splitParametricEquation( const QString equation, QString *
start++;
int length = equation.indexOf( ')' ) - start + 1;
*name = equation.mid( start, length );
*expression = equation.section( '=', 1, 1 );
*name = equation.mid( start, length ).trimmed();
*expression = equation.section( '=', 1, 1 ).trimmed();
}
......@@ -481,8 +484,8 @@ void FunctionEditor::splitImplicitEquation( const QString equation, QString * na
{
int equalsPos = equation.indexOf( '=' );
assert( equalsPos >= 0 );
*name = equation.left( equalsPos );
*expression = equation.right( equation.length() - equalsPos - 1 );
*name = equation.left( equalsPos ).trimmed();
*expression = equation.right( equation.length() - equalsPos - 1 ).trimmed();
}
......@@ -519,18 +522,11 @@ void FunctionEditor::createNewPlot()
void FunctionEditor::createCartesian()
{
kDebug() << k_funcinfo << endl;
m_functionID = -1;
// find a name not already used
QString fname( "f(x)=0" );
XParser::self()->fixFunctionName( fname, Equation::Cartesian, -1 );
QString fname;
fname = QString( "%1(x) = 0" ).arg( XParser::self()->findFunctionName( "f", -1 ) );
m_functionID = XParser::self()->Parser::addFunction( fname, QString(), Function::Cartesian );
assert( m_functionID != -1 );
kDebug() << "Created cartesian, so requestion state save.\n";
MainDlg::self()->requestSaveCurrentState();
View::self()->drawPlot();
}
......@@ -538,71 +534,45 @@ void FunctionEditor::createCartesian()
void FunctionEditor::createParametric()
{
kDebug() << k_funcinfo << endl;
m_functionID = -1;
// find a name not already used
QString fname;
XParser::self()->fixFunctionName( fname, Equation::ParametricX, -1 );
QString name = fname.mid( 1, fname.indexOf('(')-1 );
m_functionID = XParser::self()->Parser::addFunction( QString("x%1(t)=0").arg( name ), QString("y%1(t)=0").arg( name ), Function::Parametric );
QString name = XParser::self()->findFunctionName( "f", -1 );
m_functionID = XParser::self()->Parser::addFunction( QString("x%1(t) = 0").arg( name ), QString("y%1(t) = 0").arg( name ), Function::Parametric );
assert( m_functionID != -1 );
kDebug() << "Created parametric, so requesting state save.\n";
MainDlg::self()->requestSaveCurrentState();
}
void FunctionEditor::createPolar()
{
kDebug() << k_funcinfo << endl;
m_functionID = -1;
// find a name not already used
QString fname( "f(x)=0" );
XParser::self()->fixFunctionName( fname, Equation::Polar, -1 );
QString fname;
fname = QString( "%1(x) = 0" ).arg( XParser::self()->findFunctionName( "f", -1 ) );
m_functionID = XParser::self()->Parser::addFunction( fname, QString(), Function::Polar );
assert( m_functionID != -1 );
MainDlg::self()->requestSaveCurrentState();
}
void FunctionEditor::createImplicit()
{
kDebug() << k_funcinfo << endl;
m_functionID = -1;
// find a name not already used
QString fname( "f(x,y)=y*sinx + x*cosy = 1" );
XParser::self()->fixFunctionName( fname, Equation::Implicit, -1 );
QString fname;
fname = QString( "%1(x,y) = y*sinx + x*cosy = 1" ).arg( XParser::self()->findFunctionName( "f", -1 ) );
m_functionID = XParser::self()->Parser::addFunction( fname, QString(), Function::Implicit );
assert( m_functionID != -1 );
MainDlg::self()->requestSaveCurrentState();
}
void FunctionEditor::createDifferential()
{
kDebug() << k_funcinfo << endl;
m_functionID = -1;
// find a name not already used
QString fname( "f''(x)=x" );
XParser::self()->fixFunctionName( fname, Equation::Differential, -1 );
QString fname;
fname = QString( "%1''(x) = x" ).arg( XParser::self()->findFunctionName( "f", -1 ) );
m_functionID = XParser::self()->Parser::addFunction( fname, QString(), Function::Differential );
assert( m_functionID != -1 );
MainDlg::self()->requestSaveCurrentState();
View::self()->drawPlot();
}
......@@ -891,7 +861,7 @@ void FunctionEditor::saveImplicit()
m_editor->implicitName->setText(fname.mid(1,pos-1));
}