diff --git a/src/backend/core/column/ColumnPrivate.cpp b/src/backend/core/column/ColumnPrivate.cpp index 0275d20c1..9dc959992 100644 --- a/src/backend/core/column/ColumnPrivate.cpp +++ b/src/backend/core/column/ColumnPrivate.cpp @@ -1,1807 +1,1817 @@ /*************************************************************************** File : ColumnPrivate.cpp Project : AbstractColumn Description : Private data class of Column -------------------------------------------------------------------- Copyright : (C) 2007-2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2020 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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 2 of the License, 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; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ColumnPrivate.h" #include "ColumnStringIO.h" #include "Column.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/datatypes/filter.h" #include "backend/gsl/ExpressionParser.h" ColumnPrivate::ColumnPrivate(Column* owner, AbstractColumn::ColumnMode mode) : m_column_mode(mode), m_owner(owner) { Q_ASSERT(owner != nullptr); switch (mode) { case AbstractColumn::ColumnMode::Numeric: m_input_filter = new String2DoubleFilter(); m_output_filter = new Double2StringFilter('g'); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Integer: m_input_filter = new String2IntegerFilter(); m_output_filter = new Integer2StringFilter(); m_data = new QVector(); break; case AbstractColumn::ColumnMode::BigInt: m_input_filter = new String2BigIntFilter(); m_output_filter = new BigInt2StringFilter(); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); m_data = new QStringList(); break; case AbstractColumn::ColumnMode::DateTime: m_input_filter = new String2DateTimeFilter(); m_output_filter = new DateTime2StringFilter(); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Month: m_input_filter = new String2MonthFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("MMMM"); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Day: m_input_filter = new String2DayOfWeekFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("dddd"); m_data = new QVector(); break; } connect(m_output_filter, &AbstractSimpleFilter::formatChanged, m_owner, &Column::handleFormatChange); //m_input_filter->setName("InputFilter"); //m_output_filter->setName("OutputFilter"); } /** * \brief Special ctor (to be called from Column only!) */ ColumnPrivate::ColumnPrivate(Column* owner, AbstractColumn::ColumnMode mode, void* data) : m_column_mode(mode), m_data(data), m_owner(owner) { switch (mode) { case AbstractColumn::ColumnMode::Numeric: m_input_filter = new String2DoubleFilter(); m_output_filter = new Double2StringFilter(); connect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Integer: m_input_filter = new String2IntegerFilter(); m_output_filter = new Integer2StringFilter(); connect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::BigInt: m_input_filter = new String2BigIntFilter(); m_output_filter = new BigInt2StringFilter(); connect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); break; case AbstractColumn::ColumnMode::DateTime: m_input_filter = new String2DateTimeFilter(); m_output_filter = new DateTime2StringFilter(); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Month: m_input_filter = new String2MonthFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("MMMM"); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Day: m_input_filter = new String2DayOfWeekFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("dddd"); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } //m_input_filter->setName("InputFilter"); //m_output_filter->setName("OutputFilter"); } ColumnPrivate::~ColumnPrivate() { if (!m_data) return; switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: delete static_cast*>(m_data); break; case AbstractColumn::ColumnMode::Integer: delete static_cast*>(m_data); break; case AbstractColumn::ColumnMode::BigInt: delete static_cast*>(m_data); break; case AbstractColumn::ColumnMode::Text: delete static_cast*>(m_data); break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: delete static_cast*>(m_data); break; } } AbstractColumn::ColumnMode ColumnPrivate::columnMode() const { return m_column_mode; } /** * \brief Set the column mode * * This sets the column mode and, if * necessary, converts it to another datatype. * Remark: setting the mode back to undefined (the * initial value) is not supported. */ void ColumnPrivate::setColumnMode(AbstractColumn::ColumnMode mode) { DEBUG("ColumnPrivate::setColumnMode() " << ENUM_TO_STRING(AbstractColumn, ColumnMode, m_column_mode) << " -> " << ENUM_TO_STRING(AbstractColumn, ColumnMode, mode)) if (mode == m_column_mode) return; void* old_data = m_data; // remark: the deletion of the old data will be done in the dtor of a command AbstractSimpleFilter* filter = nullptr, *new_in_filter = nullptr, *new_out_filter = nullptr; bool filter_is_temporary = false; // it can also become outputFilter(), which we may not delete here Column* temp_col = nullptr; emit m_owner->modeAboutToChange(m_owner); // determine the conversion filter and allocate the new data vector switch (m_column_mode) { // old mode case AbstractColumn::ColumnMode::Numeric: { disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::ColumnMode::Numeric: break; case AbstractColumn::ColumnMode::Integer: filter = new Double2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data))); m_data = new QVector(); break; case AbstractColumn::ColumnMode::BigInt: filter = new Double2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data))); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; case AbstractColumn::ColumnMode::DateTime: filter = new Double2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Month: filter = new Double2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Day: filter = new Double2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::ColumnMode::Integer: { disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::ColumnMode::Integer: break; case AbstractColumn::ColumnMode::BigInt: filter = new Integer2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Numeric: filter = new Integer2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::DateTime: filter = new Integer2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Month: filter = new Integer2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Day: filter = new Integer2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::ColumnMode::BigInt: { disconnect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::ColumnMode::BigInt: break; case AbstractColumn::ColumnMode::Integer: filter = new BigInt2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Numeric: filter = new BigInt2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::DateTime: filter = new BigInt2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Month: filter = new BigInt2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Day: filter = new BigInt2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::ColumnMode::Text: { switch (mode) { case AbstractColumn::ColumnMode::Text: break; case AbstractColumn::ColumnMode::Numeric: filter = new String2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Integer: filter = new String2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::BigInt: filter = new String2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::DateTime: filter = new String2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Month: filter = new String2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Day: filter = new String2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: { disconnect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: break; case AbstractColumn::ColumnMode::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QStringList(); break; case AbstractColumn::ColumnMode::Numeric: if (m_column_mode == AbstractColumn::ColumnMode::Month) filter = new Month2DoubleFilter(); else if (m_column_mode == AbstractColumn::ColumnMode::Day) filter = new DayOfWeek2DoubleFilter(); else filter = new DateTime2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::Integer: if (m_column_mode == AbstractColumn::ColumnMode::Month) filter = new Month2IntegerFilter(); else if (m_column_mode == AbstractColumn::ColumnMode::Day) filter = new DayOfWeek2IntegerFilter(); else filter = new DateTime2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::ColumnMode::BigInt: if (m_column_mode == AbstractColumn::ColumnMode::Month) filter = new Month2BigIntFilter(); else if (m_column_mode == AbstractColumn::ColumnMode::Day) filter = new DayOfWeek2BigIntFilter(); else filter = new DateTime2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } } // determine the new input and output filters switch (mode) { // new mode case AbstractColumn::ColumnMode::Numeric: new_in_filter = new String2DoubleFilter(); new_out_filter = new Double2StringFilter(); connect(static_cast(new_out_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Integer: new_in_filter = new String2IntegerFilter(); new_out_filter = new Integer2StringFilter(); connect(static_cast(new_out_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::BigInt: new_in_filter = new String2BigIntFilter(); new_out_filter = new BigInt2StringFilter(); connect(static_cast(new_out_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Text: new_in_filter = new SimpleCopyThroughFilter(); new_out_filter = new SimpleCopyThroughFilter(); break; case AbstractColumn::ColumnMode::DateTime: new_in_filter = new String2DateTimeFilter(); new_out_filter = new DateTime2StringFilter(); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Month: new_in_filter = new String2MonthFilter(); new_out_filter = new DateTime2StringFilter(); static_cast(new_out_filter)->setFormat("MMMM"); DEBUG(" Month out_filter format: " << STDSTRING(static_cast(new_out_filter)->format())); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Day: new_in_filter = new String2DayOfWeekFilter(); new_out_filter = new DateTime2StringFilter(); static_cast(new_out_filter)->setFormat("dddd"); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } // switch(mode) m_column_mode = mode; //new_in_filter->setName("InputFilter"); //new_out_filter->setName("OutputFilter"); m_input_filter = new_in_filter; m_output_filter = new_out_filter; m_input_filter->input(0, m_owner->m_string_io); m_output_filter->input(0, m_owner); m_input_filter->setHidden(true); m_output_filter->setHidden(true); if (temp_col) { // if temp_col == 0, only the input/output filters need to be changed // copy the filtered, i.e. converted, column (mode is orig mode) DEBUG(" temp_col column mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, temp_col->columnMode())); filter->input(0, temp_col); DEBUG(" filter->output size = " << filter->output(0)->rowCount()); copy(filter->output(0)); delete temp_col; } if (filter_is_temporary) delete filter; emit m_owner->modeChanged(m_owner); DEBUG("ColumnPrivate::setColumnMode() DONE"); } /** * \brief Replace all mode related members * * Replace column mode, data type, data pointer and filters directly */ void ColumnPrivate::replaceModeData(AbstractColumn::ColumnMode mode, void* data, AbstractSimpleFilter* in_filter, AbstractSimpleFilter* out_filter) { DEBUG("ColumnPrivate::replaceModeData()"); emit m_owner->modeAboutToChange(m_owner); // disconnect formatChanged() switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Integer: disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::BigInt: disconnect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Text: break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: disconnect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } m_column_mode = mode; m_data = data; //in_filter->setName("InputFilter"); //out_filter->setName("OutputFilter"); m_input_filter = in_filter; m_output_filter = out_filter; m_input_filter->input(0, m_owner->m_string_io); m_output_filter->input(0, m_owner); // connect formatChanged() switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: connect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Integer: connect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::BigInt: connect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::ColumnMode::Text: break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } emit m_owner->modeChanged(m_owner); } /** * \brief Replace data pointer */ void ColumnPrivate::replaceData(void* data) { - DEBUG("ColumnPrivate::replaceData()"); + DEBUG("ColumnPrivate::replaceData()") emit m_owner->dataAboutToChange(m_owner); m_data = data; invalidate(); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool ColumnPrivate::copy(const AbstractColumn* other) { // DEBUG("ColumnPrivate::copy(other)"); if (other->columnMode() != columnMode()) return false; // DEBUG(" mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, columnMode())); int num_rows = other->rowCount(); // DEBUG(" rows " << num_rows); emit m_owner->dataAboutToChange(m_owner); resizeTo(num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } case AbstractColumn::ColumnMode::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } case AbstractColumn::ColumnMode::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->bigIntAt(i); break; } case AbstractColumn::ColumnMode::Text: { auto* vec = static_cast*>(m_data); for (int i = 0; i < num_rows; ++i) vec->replace(i, other->textAt(i)); break; } case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: { auto* vec = static_cast*>(m_data); for (int i = 0; i < num_rows; ++i) vec->replace(i, other->dateTimeAt(i)); break; } } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool ColumnPrivate::copy(const AbstractColumn* source, int source_start, int dest_start, int num_rows) { // DEBUG("ColumnPrivate::copy()"); if (source->columnMode() != m_column_mode) return false; if (num_rows == 0) return true; emit m_owner->dataAboutToChange(m_owner); if (dest_start + num_rows > rowCount()) resizeTo(dest_start + num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->valueAt(source_start + i); break; } case AbstractColumn::ColumnMode::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->integerAt(source_start + i); break; } case AbstractColumn::ColumnMode::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->bigIntAt(source_start + i); break; } case AbstractColumn::ColumnMode::Text: for (int i = 0; i < num_rows; i++) static_cast*>(m_data)->replace(dest_start+i, source->textAt(source_start + i)); break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: for (int i = 0; i < num_rows; i++) static_cast*>(m_data)->replace(dest_start+i, source->dateTimeAt(source_start + i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool ColumnPrivate::copy(const ColumnPrivate* other) { if (other->columnMode() != m_column_mode) return false; int num_rows = other->rowCount(); emit m_owner->dataAboutToChange(m_owner); resizeTo(num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } case AbstractColumn::ColumnMode::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } case AbstractColumn::ColumnMode::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->bigIntAt(i); break; } case AbstractColumn::ColumnMode::Text: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->textAt(i)); break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->dateTimeAt(i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool ColumnPrivate::copy(const ColumnPrivate* source, int source_start, int dest_start, int num_rows) { if (source->columnMode() != m_column_mode) return false; if (num_rows == 0) return true; emit m_owner->dataAboutToChange(m_owner); if (dest_start + num_rows > rowCount()) resizeTo(dest_start + num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->valueAt(source_start + i); break; } case AbstractColumn::ColumnMode::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->integerAt(source_start + i); break; } case AbstractColumn::ColumnMode::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->bigIntAt(source_start + i); break; } case AbstractColumn::ColumnMode::Text: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(dest_start+i, source->textAt(source_start + i)); break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: for (int i = 0; i *>(m_data)->replace(dest_start+i, source->dateTimeAt(source_start + i)); break; } invalidate(); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Return the data vector size * * This returns the size of the column container */ int ColumnPrivate::rowCount() const { switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: return static_cast*>(m_data)->size(); case AbstractColumn::ColumnMode::Integer: return static_cast*>(m_data)->size(); case AbstractColumn::ColumnMode::BigInt: return static_cast*>(m_data)->size(); case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: return static_cast*>(m_data)->size(); case AbstractColumn::ColumnMode::Text: return static_cast*>(m_data)->size(); } return 0; } /** * \brief Return the number of available rows * * This returns the number of rows that actually contain data. * Rows beyond this can be masked etc. but should be ignored by filters, * plots etc. */ int ColumnPrivate::availableRowCount() const { int availableRowCount = 0; for (int row = 0; row < rowCount(); row++) { if (m_owner->isValid(row) && !m_owner->isMasked(row)) availableRowCount++; } return availableRowCount; } /** * \brief Resize the vector to the specified number of rows * * Since selecting and masking rows higher than the * real internal number of rows is supported, this * does not change the interval attributes. Also * no signal is emitted. If the new rows are filled * with values AbstractColumn::dataChanged() * must be emitted. */ void ColumnPrivate::resizeTo(int new_size) { int old_size = rowCount(); if (new_size == old_size) return; // DEBUG("ColumnPrivate::resizeTo() " << old_size << " -> " << new_size); switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, NAN); break; } case AbstractColumn::ColumnMode::Integer: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, 0); break; } case AbstractColumn::ColumnMode::BigInt: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, 0); break; } case AbstractColumn::ColumnMode::Text: { int new_rows = new_size - old_size; if (new_rows > 0) { for (int i = 0; i < new_rows; ++i) static_cast*>(m_data)->append(QString()); } else { for (int i = 0; i < -new_rows; ++i) static_cast*>(m_data)->removeLast(); } break; } case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: { int new_rows = new_size - old_size; if (new_rows > 0) { for (int i = 0; i < new_rows; ++i) static_cast*>(m_data)->append(QDateTime()); } else { for (int i = 0; i < -new_rows; ++i) static_cast*>(m_data)->removeLast(); } break; } } } /** * \brief Insert some empty (or initialized with zero) rows */ void ColumnPrivate::insertRows(int before, int count) { if (count == 0) return; m_formulas.insertRows(before, count); if (before <= rowCount()) { switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: static_cast*>(m_data)->insert(before, count, NAN); break; case AbstractColumn::ColumnMode::Integer: static_cast*>(m_data)->insert(before, count, 0); break; case AbstractColumn::ColumnMode::BigInt: static_cast*>(m_data)->insert(before, count, 0); break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: for (int i = 0; i < count; ++i) static_cast*>(m_data)->insert(before, QDateTime()); break; case AbstractColumn::ColumnMode::Text: for (int i = 0; i < count; ++i) static_cast*>(m_data)->insert(before, QString()); break; } } } /** * \brief Remove 'count' rows starting from row 'first' */ void ColumnPrivate::removeRows(int first, int count) { if (count == 0) return; m_formulas.removeRows(first, count); if (first < rowCount()) { int corrected_count = count; if (first + count > rowCount()) corrected_count = rowCount() - first; switch (m_column_mode) { case AbstractColumn::ColumnMode::Numeric: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::ColumnMode::Integer: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::ColumnMode::BigInt: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Month: case AbstractColumn::ColumnMode::Day: for (int i = 0; i < corrected_count; ++i) static_cast*>(m_data)->removeAt(first); break; case AbstractColumn::ColumnMode::Text: for (int i = 0; i < corrected_count; ++i) static_cast*>(m_data)->removeAt(first); break; } } } //! Return the column name QString ColumnPrivate::name() const { return m_owner->name(); } /** * \brief Return the column plot designation */ AbstractColumn::PlotDesignation ColumnPrivate::plotDesignation() const { return m_plot_designation; } /** * \brief Set the column plot designation */ void ColumnPrivate::setPlotDesignation(AbstractColumn::PlotDesignation pd) { emit m_owner->plotDesignationAboutToChange(m_owner); m_plot_designation = pd; emit m_owner->plotDesignationChanged(m_owner); } /** * \brief Get width */ int ColumnPrivate::width() const { return m_width; } /** * \brief Set width */ void ColumnPrivate::setWidth(int value) { m_width = value; } /** * \brief Return the data pointer */ void* ColumnPrivate::data() const { return m_data; } /** * \brief Return the input filter (for string -> data type conversion) */ AbstractSimpleFilter *ColumnPrivate::inputFilter() const { return m_input_filter; } /** * \brief Return the output filter (for data type -> string conversion) */ AbstractSimpleFilter *ColumnPrivate::outputFilter() const { return m_output_filter; } //////////////////////////////////////////////////////////////////////////////// //! \name Formula related functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the formula last used to generate data for the column */ QString ColumnPrivate::formula() const { return m_formula; } bool ColumnPrivate::formulaAutoUpdate() const { return m_formulaAutoUpdate; } /** * \brief Sets the formula used to generate column values */ void ColumnPrivate::setFormula(const QString& formula, const QStringList& variableNames, const QVector& variableColumns, bool autoUpdate) { m_formula = formula; m_formulaVariableNames = variableNames; m_formulaVariableColumns = variableColumns; m_formulaAutoUpdate = autoUpdate; for (auto connection: m_connectionsUpdateFormula) - disconnect(connection); + if (static_cast(connection)) + disconnect(connection); m_formulaVariableColumnPaths.clear(); for (auto column : variableColumns) { m_formulaVariableColumnPaths << column->path(); if (autoUpdate) connectFormulaColumn(column); } } /*! * called after the import of the project was done and all columns were loaded in \sa Project::load() * to establish the required slot-signal connections for the formula update */ void ColumnPrivate::finalizeLoad() { if (m_formulaAutoUpdate) { for (auto column : m_formulaVariableColumns) connectFormulaColumn(column); } } /*! * \brief ColumnPrivate::connectFormulaColumn * This function is used to connect the columns to the needed slots for updating formulas * \param column */ void ColumnPrivate::connectFormulaColumn(const AbstractColumn* column) { if (!column) return; - m_connectionsUpdateFormula << connect(column, &Column::dataChanged, m_owner, &Column::updateFormula); + DEBUG("ColumnPrivate::connectFormulaColumn()") + m_connectionsUpdateFormula << connect(column, &AbstractColumn::dataChanged, m_owner, &Column::updateFormula); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &ColumnPrivate::formulaVariableColumnRemoved); connect(column, &AbstractColumn::reset, this, &ColumnPrivate::formulaVariableColumnRemoved); connect(column->parentAspect(), &AbstractAspect::aspectAdded, this, &ColumnPrivate::formulaVariableColumnAdded); } /*! * helper function used in \c Column::load() to set parameters read from the xml file. * \param variableColumnPaths is used to restore the pointers to columns from pathes * after the project was loaded in Project::load(). */ void ColumnPrivate::setFormula(const QString& formula, const QStringList& variableNames, const QStringList& variableColumnPaths, bool autoUpdate) { m_formula = formula; m_formulaVariableNames = variableNames; m_formulaVariableColumnPaths = variableColumnPaths; m_formulaVariableColumns.resize(variableColumnPaths.length()); m_formulaAutoUpdate = autoUpdate; } const QStringList& ColumnPrivate::formulaVariableNames() const { return m_formulaVariableNames; } const QVector& ColumnPrivate::formulaVariableColumns() const { return m_formulaVariableColumns; } const QStringList& ColumnPrivate::formulaVariableColumnPaths() const { return m_formulaVariableColumnPaths; } void ColumnPrivate::setformulVariableColumnsPath(int index, const QString& path) { m_formulaVariableColumnPaths[index] = path; } void ColumnPrivate::setformulVariableColumn(int index, Column* column) { if (m_formulaVariableColumns[index]) // if there exists already a valid column, disconnect it first disconnect(m_formulaVariableColumns[index], nullptr, this, nullptr); m_formulaVariableColumns[index] = column; connectFormulaColumn(column); } /*! * \sa FunctionValuesDialog::generate() */ void ColumnPrivate::updateFormula() { + DEBUG("ColumnPrivate::updateFormula()") //determine variable names and the data vectors of the specified columns QVector*> xVectors; QVector*> xNewVectors; int maxRowCount = 0; bool valid = true; for (auto column : m_formulaVariableColumns) { if (!column) { valid = false; break; } if (column->columnMode() == AbstractColumn::ColumnMode::Integer || column->columnMode() == AbstractColumn::ColumnMode::BigInt) { //convert integers to doubles first auto* xVector = new QVector(column->rowCount()); for (int i = 0; irowCount(); ++i) xVector->operator[](i) = column->valueAt(i); xNewVectors << xVector; xVectors << xVector; } else xVectors << static_cast* >(column->data()); if (column->rowCount() > maxRowCount) maxRowCount = column->rowCount(); } if (valid) { //resize the spreadsheet if one of the data vectors from //other spreadsheet(s) has more elements than the parent spreadsheet Spreadsheet* spreadsheet = static_cast(m_owner->parentAspect()); if (spreadsheet->rowCount() < maxRowCount) spreadsheet->setRowCount(maxRowCount); //create new vector for storing the calculated values //the vectors with the variable data can be smaller then the result vector. So, not all values in the result vector might get initialized. //->"clean" the result vector first QVector new_data(rowCount(), NAN); //evaluate the expression for f(x_1, x_2, ...) and write the calculated values into a new vector. ExpressionParser* parser = ExpressionParser::getInstance(); + DEBUG("Calling evaluateCartesian()") parser->evaluateCartesian(m_formula, m_formulaVariableNames, xVectors, &new_data); + DEBUG("Calling replaceValues()") replaceValues(0, new_data); // initialize remaining rows with NAN int remainingRows = rowCount() - maxRowCount; if (remainingRows > 0) { QVector emptyRows(remainingRows, NAN); replaceValues(maxRowCount, emptyRows); } } else { QVector new_data(rowCount(), NAN); replaceValues(0, new_data); } //delete help vectors created for the conversion from int to double for (auto* vector : xNewVectors) delete vector; + + DEBUG("ColumnPrivate::updateFormula() DONE") } void ColumnPrivate::formulaVariableColumnRemoved(const AbstractAspect* aspect) { const Column* column = dynamic_cast(aspect); disconnect(column, nullptr, this, nullptr); //TODO: why is const_cast required here?!? int index = m_formulaVariableColumns.indexOf(const_cast(column)); if (index != -1) { m_formulaVariableColumns[index] = nullptr; + DEBUG("ColumnPrivate::formulaVariableColumnRemoved():updateFormula()") updateFormula(); } } void ColumnPrivate::formulaVariableColumnAdded(const AbstractAspect* aspect) { int index = m_formulaVariableColumnPaths.indexOf(aspect->path()); if (index != -1) { const Column* column = dynamic_cast(aspect); m_formulaVariableColumns[index] = const_cast(column); + DEBUG("ColumnPrivate::formulaVariableColumnAdded():updateFormula()") updateFormula(); } } /** * \brief Return the formula associated with row 'row' */ QString ColumnPrivate::formula(int row) const { return m_formulas.value(row); } /** * \brief Return the intervals that have associated formulas * * This can be used to make a list of formulas with their intervals. * Here is some example code: * * \code * QStringList list; * QVector< Interval > intervals = my_column.formulaIntervals(); * foreach(Interval interval, intervals) * list << QString(interval.toString() + ": " + my_column.formula(interval.start())); * \endcode */ QVector< Interval > ColumnPrivate::formulaIntervals() const { return m_formulas.intervals(); } /** * \brief Set a formula string for an interval of rows */ void ColumnPrivate::setFormula(const Interval& i, const QString& formula) { m_formulas.setValue(i, formula); } /** * \brief Overloaded function for convenience */ void ColumnPrivate::setFormula(int row, const QString& formula) { setFormula(Interval(row,row), formula); } /** * \brief Clear all formulas */ void ColumnPrivate::clearFormulas() { m_formulas.clear(); } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //! \name type specific functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the content of row 'row'. * * Use this only when columnMode() is Text */ QString ColumnPrivate::textAt(int row) const { if (m_column_mode != AbstractColumn::ColumnMode::Text) return QString(); return static_cast*>(m_data)->value(row); } /** * \brief Return the date part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDate ColumnPrivate::dateAt(int row) const { if (m_column_mode != AbstractColumn::ColumnMode::DateTime && m_column_mode != AbstractColumn::ColumnMode::Month && m_column_mode != AbstractColumn::ColumnMode::Day) return QDate{}; return dateTimeAt(row).date(); } /** * \brief Return the time part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QTime ColumnPrivate::timeAt(int row) const { if (m_column_mode != AbstractColumn::ColumnMode::DateTime && m_column_mode != AbstractColumn::ColumnMode::Month && m_column_mode != AbstractColumn::ColumnMode::Day) return QTime{}; return dateTimeAt(row).time(); } /** * \brief Return the QDateTime in row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDateTime ColumnPrivate::dateTimeAt(int row) const { if (m_column_mode != AbstractColumn::ColumnMode::DateTime && m_column_mode != AbstractColumn::ColumnMode::Month && m_column_mode != AbstractColumn::ColumnMode::Day) return QDateTime(); return static_cast*>(m_data)->value(row); } /** * \brief Return the double value in row 'row' for columns with type Numeric, Integer or BigInt. * This function has to be used everywhere where the exact type (double, int or qint64) is not relevant for numerical calculations. * For cases where the integer value is needed without any implicit conversions, \sa integerAt() has to be used. */ double ColumnPrivate::valueAt(int row) const { if (m_column_mode == AbstractColumn::ColumnMode::Numeric) return static_cast*>(m_data)->value(row, NAN); else if (m_column_mode == AbstractColumn::ColumnMode::Integer) return static_cast*>(m_data)->value(row, 0); else if (m_column_mode == AbstractColumn::ColumnMode::BigInt) return static_cast*>(m_data)->value(row, 0); else return NAN; } /** * \brief Return the int value in row 'row' */ int ColumnPrivate::integerAt(int row) const { if (m_column_mode != AbstractColumn::ColumnMode::Integer) return 0; return static_cast*>(m_data)->value(row, 0); } /** * \brief Return the bigint value in row 'row' */ qint64 ColumnPrivate::bigIntAt(int row) const { if (m_column_mode != AbstractColumn::ColumnMode::BigInt) return 0; return static_cast*>(m_data)->value(row, 0); } void ColumnPrivate::invalidate() { statisticsAvailable = false; hasValuesAvailable = false; propertiesAvailable = false; } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Text */ void ColumnPrivate::setTextAt(int row, const QString& new_value) { if (m_column_mode != AbstractColumn::ColumnMode::Text) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row + 1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Text */ void ColumnPrivate::replaceTexts(int first, const QVector& new_values) { if (m_column_mode != AbstractColumn::ColumnMode::Text) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(first+i, new_values.at(i)); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setDateAt(int row, QDate new_value) { if (m_column_mode != AbstractColumn::ColumnMode::DateTime && m_column_mode != AbstractColumn::ColumnMode::Month && m_column_mode != AbstractColumn::ColumnMode::Day) return; setDateTimeAt(row, QDateTime(new_value, timeAt(row))); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setTimeAt(int row, QTime new_value) { if (m_column_mode != AbstractColumn::ColumnMode::DateTime && m_column_mode != AbstractColumn::ColumnMode::Month && m_column_mode != AbstractColumn::ColumnMode::Day) return; setDateTimeAt(row, QDateTime(dateAt(row), new_value)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setDateTimeAt(int row, const QDateTime& new_value) { if (m_column_mode != AbstractColumn::ColumnMode::DateTime && m_column_mode != AbstractColumn::ColumnMode::Month && m_column_mode != AbstractColumn::ColumnMode::Day) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast< QVector* >(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::replaceDateTimes(int first, const QVector& new_values) { if (m_column_mode != AbstractColumn::ColumnMode::DateTime && m_column_mode != AbstractColumn::ColumnMode::Month && m_column_mode != AbstractColumn::ColumnMode::Day) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(first+i, new_values.at(i)); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Numeric */ void ColumnPrivate::setValueAt(int row, double new_value) { // DEBUG("ColumnPrivate::setValueAt()"); if (m_column_mode != AbstractColumn::ColumnMode::Numeric) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Numeric */ void ColumnPrivate::replaceValues(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceValues()"); + if (m_column_mode != AbstractColumn::ColumnMode::Numeric) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Integer */ void ColumnPrivate::setIntegerAt(int row, int new_value) { DEBUG("ColumnPrivate::setIntegerAt()"); if (m_column_mode != AbstractColumn::ColumnMode::Integer) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Integer */ void ColumnPrivate::replaceInteger(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceInteger()"); if (m_column_mode != AbstractColumn::ColumnMode::Integer) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is BigInt */ void ColumnPrivate::setBigIntAt(int row, qint64 new_value) { DEBUG("ColumnPrivate::setBigIntAt()"); if (m_column_mode != AbstractColumn::ColumnMode::BigInt) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is BigInt */ void ColumnPrivate::replaceBigInt(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceBigInt()"); if (m_column_mode != AbstractColumn::ColumnMode::BigInt) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /*! * Updates the properties. Will be called, when data in the column changed. * The properties will be used to speed up some algorithms. * See where variable properties will be used. */ void ColumnPrivate::updateProperties() { // TODO: for double Properties::Constant will never be used. Use an epsilon (difference smaller than epsilon is zero) if (rowCount() == 0) { properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } double prevValue = NAN; int prevValueInt = 0; qint64 prevValueBigInt = 0; qint64 prevValueDatetime = 0; if (m_column_mode == AbstractColumn::ColumnMode::Integer) prevValueInt = integerAt(0); else if (m_column_mode == AbstractColumn::ColumnMode::BigInt) prevValueBigInt = bigIntAt(0); else if (m_column_mode == AbstractColumn::ColumnMode::Numeric) prevValue = valueAt(0); else if (m_column_mode == AbstractColumn::ColumnMode::DateTime || m_column_mode == AbstractColumn::ColumnMode::Month || m_column_mode == AbstractColumn::ColumnMode::Day) prevValueDatetime = dateTimeAt(0).toMSecsSinceEpoch(); else { properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } int monotonic_decreasing = -1; int monotonic_increasing = -1; double value; int valueInt; qint64 valueBigInt; qint64 valueDateTime; for (int row = 1; row < rowCount(); row++) { if (!m_owner->isValid(row) || m_owner->isMasked(row)) { // if there is one invalid or masked value, the property is No, because // otherwise it's difficult to find the correct index in indexForValue(). // You don't know if you should increase the index or decrease it when // you hit an invalid value properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } if (m_column_mode == AbstractColumn::ColumnMode::Integer) { valueInt = integerAt(row); if (valueInt > prevValueInt) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (valueInt < prevValueInt) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValueInt = valueInt; } else if (m_column_mode == AbstractColumn::ColumnMode::BigInt) { valueBigInt = bigIntAt(row); if (valueBigInt > prevValueBigInt) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (valueBigInt < prevValueBigInt) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValueBigInt = valueBigInt; } else if (m_column_mode == AbstractColumn::ColumnMode::Numeric) { value = valueAt(row); if (std::isnan(value)) { monotonic_increasing = 0; monotonic_decreasing = 0; break; } if (value > prevValue) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (value < prevValue) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValue = value; } else if (m_column_mode == AbstractColumn::ColumnMode::DateTime || m_column_mode == AbstractColumn::ColumnMode::Month || m_column_mode == AbstractColumn::ColumnMode::Day) { valueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); if (valueDateTime > prevValueDatetime) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (valueDateTime < prevValueDatetime) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValueDatetime = valueDateTime; } } properties = AbstractColumn::Properties::No; if (monotonic_increasing > 0 && monotonic_decreasing > 0) properties = AbstractColumn::Properties::Constant; else if (monotonic_decreasing > 0) properties = AbstractColumn::Properties::MonotonicDecreasing; else if (monotonic_increasing > 0) properties = AbstractColumn::Properties::MonotonicIncreasing; propertiesAvailable = true; } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the interval attribute representing the formula strings */ IntervalAttribute ColumnPrivate::formulaAttribute() const { return m_formulas; } /** * \brief Replace the interval attribute for the formula strings */ void ColumnPrivate::replaceFormulas(const IntervalAttribute& formulas) { m_formulas = formulas; } diff --git a/src/backend/gsl/ExpressionParser.cpp b/src/backend/gsl/ExpressionParser.cpp index b6dd1f151..8bc5c4242 100644 --- a/src/backend/gsl/ExpressionParser.cpp +++ b/src/backend/gsl/ExpressionParser.cpp @@ -1,1563 +1,1570 @@ /*************************************************************************** File : ExpressionParser.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2014 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2014-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Description : C++ wrapper for the bison generated parser. ***************************************************************************/ /*************************************************************************** * * * 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 2 of the License, 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; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include #include "backend/lib/macros.h" #include "backend/gsl/ExpressionParser.h" #include #include extern "C" { #include #include #include #include #include #include "backend/gsl/parser.h" } ExpressionParser* ExpressionParser::instance = nullptr; ExpressionParser::ExpressionParser() { init_table(); initFunctions(); initConstants(); } void ExpressionParser::initFunctions() { //functions (sync with functions.h!) for (int i = 0; _functions[i].name != nullptr; i++) m_functions << _functions[i].name; m_functionsGroups << i18n("Standard Mathematical functions"); //https://www.gnu.org/software/gsl/doc/html/specfunc.html m_functionsGroups << i18n("Airy Functions and Derivatives"); m_functionsGroups << i18n("Bessel Functions"); m_functionsGroups << i18n("Clausen Functions"); m_functionsGroups << i18n("Coulomb Functions"); // m_functionsGroups << i18n("Coupling Coefficients"); m_functionsGroups << i18n("Dawson Function"); m_functionsGroups << i18n("Debye Functions"); m_functionsGroups << i18n("Dilogarithm"); // m_functionsGroups << i18n("Elementary Operations"); m_functionsGroups << i18n("Elliptic Integrals"); // m_functionsGroups << i18n("Elliptic Functions (Jacobi)"); #ifndef _MSC_VER m_functionsGroups << i18n("Error Functions and Related Functions"); #else m_functionsGroups << i18n("Error Functions"); #endif m_functionsGroups << i18n("Exponential Functions"); m_functionsGroups << i18n("Exponential Integrals"); m_functionsGroups << i18n("Fermi-Dirac Function"); m_functionsGroups << i18n("Gamma and Beta Functions"); m_functionsGroups << i18n("Gegenbauer Functions"); #if (GSL_MAJOR_VERSION > 2) || (GSL_MAJOR_VERSION == 2) && (GSL_MINOR_VERSION >= 4) m_functionsGroups << i18n("Hermite Polynomials and Functions"); #endif m_functionsGroups << i18n("Hypergeometric Functions"); m_functionsGroups << i18n("Laguerre Functions"); m_functionsGroups << i18n("Lambert W Functions"); m_functionsGroups << i18n("Legendre Functions and Spherical Harmonics"); m_functionsGroups << i18n("Logarithm and Related Functions"); // m_functionsGroups << i18n("Mathieu Functions"); m_functionsGroups << i18n("Power Function"); m_functionsGroups << i18n("Psi (Digamma) Function"); m_functionsGroups << i18n("Synchrotron Functions"); m_functionsGroups << i18n("Transport Functions"); m_functionsGroups << i18n("Trigonometric Functions"); m_functionsGroups << i18n("Zeta Functions"); // GSL random distribution functions m_functionsGroups << i18n("Gaussian Distribution"); m_functionsGroups << i18n("Exponential Distribution"); m_functionsGroups << i18n("Laplace Distribution"); m_functionsGroups << i18n("Exponential Power Distribution"); m_functionsGroups << i18n("Cauchy Distribution"); m_functionsGroups << i18n("Rayleigh Distribution"); m_functionsGroups << i18n("Landau Distribution"); m_functionsGroups << i18n("Gamma Distribution"); m_functionsGroups << i18n("Flat (Uniform) Distribution"); m_functionsGroups << i18n("Lognormal Distribution"); m_functionsGroups << i18n("Chi-squared Distribution"); m_functionsGroups << i18n("F-distribution"); m_functionsGroups << i18n("t-distribution"); m_functionsGroups << i18n("Beta Distribution"); m_functionsGroups << i18n("Logistic Distribution"); m_functionsGroups << i18n("Pareto Distribution"); m_functionsGroups << i18n("Weibull Distribution"); m_functionsGroups << i18n("Gumbel Distribution"); m_functionsGroups << i18n("Poisson Distribution"); m_functionsGroups << i18n("Bernoulli Distribution"); m_functionsGroups << i18n("Binomial Distribution"); m_functionsGroups << i18n("Pascal Distribution"); m_functionsGroups << i18n("Geometric Distribution"); m_functionsGroups << i18n("Hypergeometric Distribution"); m_functionsGroups << i18n("Logarithmic Distribution"); int index = 0; // Standard mathematical functions m_functionsNames << i18n("pseudo-random integer [0,RAND_MAX]"); m_functionsNames << i18n("nonlinear additive feedback rng [0,RAND_MAX]"); m_functionsNames << i18n("nonlinear additive feedback rng [0,1]"); m_functionsNames << i18n("Smallest integral value not less"); m_functionsNames << i18n("Absolute value"); m_functionsNames << i18n("Base 10 logarithm"); m_functionsNames << i18n("Power function [x^y]"); m_functionsNames << i18n("Nonnegative square root"); m_functionsNames << i18n("Sign function"); m_functionsNames << i18n("Heavyside theta function"); m_functionsNames << i18n("Harmonic number function"); #ifndef HAVE_WINDOWS m_functionsNames << i18n("Cube root"); m_functionsNames << i18n("Extract the exponent"); m_functionsNames << i18n("Round to an integer value"); m_functionsNames << i18n("Round to the nearest integer"); m_functionsNames << i18n("Round to the nearest integer"); #endif m_functionsNames << QString("log(1+x)"); m_functionsNames << QString("x * 2^e"); m_functionsNames << QString("x^n"); m_functionsNames << QString("x^2"); m_functionsNames << QString("x^3"); m_functionsNames << QString("x^4"); m_functionsNames << QString("x^5"); m_functionsNames << QString("x^6"); m_functionsNames << QString("x^7"); m_functionsNames << QString("x^8"); m_functionsNames << QString("x^9"); #ifndef HAVE_WINDOWS for (int i = 0; i < 27; i++) #else for (int i = 0; i < 22; i++) #endif m_functionsGroupIndex << index; // Airy Functions and Derivatives m_functionsNames << i18n("Airy function of the first kind"); m_functionsNames << i18n("Airy function of the second kind"); m_functionsNames << i18n("Scaled Airy function of the first kind"); m_functionsNames << i18n("Scaled Airy function of the second kind"); m_functionsNames << i18n("Airy function derivative of the first kind"); m_functionsNames << i18n("Airy function derivative of the second kind"); m_functionsNames << i18n("Scaled Airy function derivative of the first kind"); m_functionsNames << i18n("Scaled Airy function derivative of the second kind"); m_functionsNames << i18n("n-th zero of the Airy function of the first kind"); m_functionsNames << i18n("n-th zero of the Airy function of the second kind"); m_functionsNames << i18n("n-th zero of the Airy function derivative of the first kind"); m_functionsNames << i18n("n-th zero of the Airy function derivative of the second kind"); index++; for (int i = 0; i < 12; i++) m_functionsGroupIndex << index; // Bessel Functions m_functionsNames << i18n("Regular cylindrical Bessel function of zeroth order"); m_functionsNames << i18n("Regular cylindrical Bessel function of first order"); m_functionsNames << i18n("Regular cylindrical Bessel function of order n"); m_functionsNames << i18n("Irregular cylindrical Bessel function of zeroth order"); m_functionsNames << i18n("Irregular cylindrical Bessel function of first order"); m_functionsNames << i18n("Irregular cylindrical Bessel function of order n"); m_functionsNames << i18n("Regular modified cylindrical Bessel function of zeroth order"); m_functionsNames << i18n("Regular modified cylindrical Bessel function of first order"); m_functionsNames << i18n("Regular modified cylindrical Bessel function of order n"); m_functionsNames << i18n("Scaled regular modified cylindrical Bessel function of zeroth order exp(-|x|) I0(x)"); m_functionsNames << i18n("Scaled regular modified cylindrical Bessel function of first order exp(-|x|) I1(x)"); m_functionsNames << i18n("Scaled regular modified cylindrical Bessel function of order n exp(-|x|) In(x)"); m_functionsNames << i18n("Irregular modified cylindrical Bessel function of zeroth order"); m_functionsNames << i18n("Irregular modified cylindrical Bessel function of first order"); m_functionsNames << i18n("Irregular modified cylindrical Bessel function of order n"); m_functionsNames << i18n("Scaled irregular modified cylindrical Bessel function of zeroth order exp(x) K0(x)"); m_functionsNames << i18n("Scaled irregular modified cylindrical Bessel function of first order exp(x) K1(x)"); m_functionsNames << i18n("Scaled irregular modified cylindrical Bessel function of order n exp(x) Kn(x)"); m_functionsNames << i18n("Regular spherical Bessel function of zeroth order"); m_functionsNames << i18n("Regular spherical Bessel function of first order"); m_functionsNames << i18n("Regular spherical Bessel function of second order"); m_functionsNames << i18n("Regular spherical Bessel function of order l"); m_functionsNames << i18n("Irregular spherical Bessel function of zeroth order"); m_functionsNames << i18n("Irregular spherical Bessel function of first order"); m_functionsNames << i18n("Irregular spherical Bessel function of second order"); m_functionsNames << i18n("Irregular spherical Bessel function of order l"); m_functionsNames << i18n("Scaled regular modified spherical Bessel function of zeroth order, exp(-|x|) i0(x)"); m_functionsNames << i18n("Scaled regular modified spherical Bessel function of first order, exp(-|x|) i1(x)"); m_functionsNames << i18n("Scaled regular modified spherical Bessel function of second order, exp(-|x|) i2(x)"); m_functionsNames << i18n("Scaled regular modified spherical Bessel function of order l, exp(-|x|) il(x)"); m_functionsNames << i18n("Scaled irregular modified spherical Bessel function of zeroth order, exp(x) k0(x)"); m_functionsNames << i18n("Scaled irregular modified spherical Bessel function of first order, exp(-|x|) k1(x)"); m_functionsNames << i18n("Scaled irregular modified spherical Bessel function of second order, exp(-|x|) k2(x)"); m_functionsNames << i18n("Scaled irregular modified spherical Bessel function of order l, exp(-|x|) kl(x)"); m_functionsNames << i18n("Regular cylindrical Bessel function of fractional order"); m_functionsNames << i18n("Irregular cylindrical Bessel function of fractional order"); m_functionsNames << i18n("Regular modified Bessel function of fractional order"); m_functionsNames << i18n("Scaled regular modified Bessel function of fractional order"); m_functionsNames << i18n("Irregular modified Bessel function of fractional order"); m_functionsNames << i18n("Logarithm of irregular modified Bessel function of fractional order"); m_functionsNames << i18n("Scaled irregular modified Bessel function of fractional order"); m_functionsNames << i18n("n-th positive zero of the Bessel function J0"); m_functionsNames << i18n("n-th positive zero of the Bessel function J1"); m_functionsNames << i18n("n-th positive zero of the Bessel function Jnu"); index++; for (int i = 0; i < 44; i++) m_functionsGroupIndex << index; // Clausen Functions m_functionsNames << i18n("Clausen function"); index++; m_functionsGroupIndex << index; // Coulomb Functions m_functionsNames << i18n("Lowest-order normalized hydrogenic bound state radial wavefunction"); m_functionsNames << i18n("n-th normalized hydrogenic bound state radial wavefunction"); index++; for (int i = 0; i < 2; i++) m_functionsGroupIndex << index; // Dawson Function m_functionsNames << i18n("Dawson integral"); index++; m_functionsGroupIndex << index; // Debye Functions m_functionsNames << i18n("First-order Debye function"); m_functionsNames << i18n("Second-order Debye function"); m_functionsNames << i18n("Third-order Debye function"); m_functionsNames << i18n("Fourth-order Debye function"); m_functionsNames << i18n("Fifth-order Debye function"); m_functionsNames << i18n("Sixth-order Debye function"); index++; for (int i = 0; i < 6; i++) m_functionsGroupIndex << index; // Dilogarithm m_functionsNames << i18n("Dilogarithm for a real argument"); index++; m_functionsGroupIndex << index; // Elliptic Integrals m_functionsNames << i18n("Legendre form of complete elliptic integral K"); m_functionsNames << i18n("Legendre form of complete elliptic integral E"); m_functionsNames << i18n("Legendre form of complete elliptic integral Pi"); m_functionsNames << i18n("Legendre form of incomplete elliptic integral F"); m_functionsNames << i18n("Legendre form of incomplete elliptic integral E"); m_functionsNames << i18n("Legendre form of incomplete elliptic integral P"); m_functionsNames << i18n("Legendre form of incomplete elliptic integral D"); m_functionsNames << i18n("Carlson form of incomplete elliptic integral RC"); m_functionsNames << i18n("Carlson form of incomplete elliptic integral RD"); m_functionsNames << i18n("Carlson form of incomplete elliptic integral RF"); m_functionsNames << i18n("Carlson form of incomplete elliptic integral RJ"); index++; for (int i = 0; i < 11; i++) m_functionsGroupIndex << index; // Error Functions m_functionsNames << i18n("Error function"); m_functionsNames << i18n("Complementary error function"); m_functionsNames << i18n("Logarithm of complementary error function"); m_functionsNames << i18n("Gaussian probability density function Z"); m_functionsNames << i18n("Upper tail of the Gaussian probability function Q"); m_functionsNames << i18n("Hazard function for the normal distribution Z/Q"); int count = 6; #ifndef _MSC_VER m_functionsNames << i18n("Underflow-compensating function exp(x^2) erfc(x) for real x"); m_functionsNames << i18n("Imaginary error function erfi(x) = -i erf(ix) for real x"); m_functionsNames << i18n("Imaginary part of Faddeeva's scaled complex error function w(x) = exp(-x^2) erfc(-ix) for real x"); m_functionsNames << i18n("Dawson's integral D(z) = sqrt(pi)/2 * exp(-z^2) * erfi(z)"); m_functionsNames << i18n("Voigt profile"); count += 5; #endif m_functionsNames << i18n("Pseudo-Voigt profile (same width)"); count += 1; index++; for (int i = 0; i < count; i++) m_functionsGroupIndex << index; // Exponential Functions m_functionsNames << i18n("Exponential function"); m_functionsNames << i18n("exponentiate x and multiply by y"); m_functionsNames << QString("exp(x) - 1"); m_functionsNames << QString("(exp(x)-1)/x"); m_functionsNames << QString("2(exp(x)-1-x)/x^2"); m_functionsNames << i18n("n-relative exponential"); index++; for (int i = 0; i < 6; i++) m_functionsGroupIndex << index; // Exponential Integrals m_functionsNames << i18n("Exponential integral"); m_functionsNames << i18n("Second order exponential integral"); m_functionsNames << i18n("Exponential integral of order n"); m_functionsNames << i18n("Exponential integral Ei"); m_functionsNames << i18n("Hyperbolic integral Shi"); m_functionsNames << i18n("Hyperbolic integral Chi"); m_functionsNames << i18n("Third-order exponential integral"); m_functionsNames << i18n("Sine integral"); m_functionsNames << i18n("Cosine integral"); m_functionsNames << i18n("Arctangent integral"); index++; for (int i = 0; i < 10; i++) m_functionsGroupIndex << index; // Fermi-Dirac Function m_functionsNames << i18n("Complete Fermi-Dirac integral with index -1"); m_functionsNames << i18n("Complete Fermi-Dirac integral with index 0"); m_functionsNames << i18n("Complete Fermi-Dirac integral with index 1"); m_functionsNames << i18n("Complete Fermi-Dirac integral with index 2"); m_functionsNames << i18n("Complete Fermi-Dirac integral with integer index j"); m_functionsNames << i18n("Complete Fermi-Dirac integral with index -1/2"); m_functionsNames << i18n("Complete Fermi-Dirac integral with index 1/2"); m_functionsNames << i18n("Complete Fermi-Dirac integral with index 3/2"); m_functionsNames << i18n("Incomplete Fermi-Dirac integral with index zero"); index++; for (int i = 0; i < 9; i++) m_functionsGroupIndex << index; // Gamma and Beta Functions m_functionsNames << i18n("Gamma function"); m_functionsNames << i18n("Gamma function"); m_functionsNames << i18n("Logarithm of the gamma function"); m_functionsNames << i18n("Logarithm of the gamma function"); m_functionsNames << i18n("Regulated gamma function"); m_functionsNames << i18n("Reciprocal of the gamma function"); m_functionsNames << i18n("Factorial n!"); m_functionsNames << i18n("Double factorial n!!"); m_functionsNames << i18n("Logarithm of the factorial"); m_functionsNames << i18n("Logarithm of the double factorial"); m_functionsNames << i18n("Combinatorial factor"); m_functionsNames << i18n("Logarithm of the combinatorial factor"); m_functionsNames << i18n("Taylor coefficient"); m_functionsNames << i18n("Pochhammer symbol"); m_functionsNames << i18n("Logarithm of the Pochhammer symbol"); m_functionsNames << i18n("Relative Pochhammer symbol"); m_functionsNames << i18n("Unnormalized incomplete gamma function"); m_functionsNames << i18n("Normalized incomplete gamma function"); m_functionsNames << i18n("Complementary normalized incomplete gamma function"); m_functionsNames << i18n("Beta function"); m_functionsNames << i18n("Logarithm of the beta function"); m_functionsNames << i18n("Normalized incomplete beta function"); index++; for (int i = 0; i < 22; i++) m_functionsGroupIndex << index; // Gegenbauer Functions m_functionsNames << i18n("Gegenbauer polynomial C_1"); m_functionsNames << i18n("Gegenbauer polynomial C_2"); m_functionsNames << i18n("Gegenbauer polynomial C_3"); m_functionsNames << i18n("Gegenbauer polynomial C_n"); index++; for (int i = 0; i < 4; i++) m_functionsGroupIndex << index; #if (GSL_MAJOR_VERSION > 2) || (GSL_MAJOR_VERSION == 2) && (GSL_MINOR_VERSION >= 4) // Hermite Polynomials and Functions m_functionsNames << i18n("Hermite polynomials physicists version"); m_functionsNames << i18n("Hermite polynomials probabilists version"); m_functionsNames << i18n("Hermite functions"); m_functionsNames << i18n("Derivatives of Hermite polynomials physicists version"); m_functionsNames << i18n("Derivatives of Hermite polynomials probabilists version"); m_functionsNames << i18n("Derivatives of Hermite functions"); index++; for (int i = 0; i < 6; i++) m_functionsGroupIndex << index; #endif // Hypergeometric Functions m_functionsNames << i18n("Hypergeometric function 0F1"); m_functionsNames << i18n("Confluent hypergeometric function 1F1 for integer parameters"); m_functionsNames << i18n("Confluent hypergeometric function 1F1 for general parameters"); m_functionsNames << i18n("Confluent hypergeometric function U for integer parameters"); m_functionsNames << i18n("Confluent hypergeometric function U"); m_functionsNames << i18n("Gauss hypergeometric function 2F1"); m_functionsNames << i18n("Gauss hypergeometric function 2F1 with complex parameters"); m_functionsNames << i18n("Renormalized Gauss hypergeometric function 2F1"); m_functionsNames << i18n("Renormalized Gauss hypergeometric function 2F1 with complex parameters"); m_functionsNames << i18n("Hypergeometric function 2F0"); index++; for (int i = 0; i < 10; i++) m_functionsGroupIndex << index; // Laguerre Functions m_functionsNames << i18n("generalized Laguerre polynomials L_1"); m_functionsNames << i18n("generalized Laguerre polynomials L_2"); m_functionsNames << i18n("generalized Laguerre polynomials L_3"); index++; for (int i = 0; i < 3; i++) m_functionsGroupIndex << index; // Lambert W Functions m_functionsNames << i18n("Principal branch of the Lambert W function"); m_functionsNames << i18n("Secondary real-valued branch of the Lambert W function"); index++; for (int i = 0; i < 2; i++) m_functionsGroupIndex << index; // Legendre Functions and Spherical Harmonics m_functionsNames << i18n("Legendre polynomial P_1"); m_functionsNames << i18n("Legendre polynomial P_2"); m_functionsNames << i18n("Legendre polynomial P_3"); m_functionsNames << i18n("Legendre polynomial P_l"); m_functionsNames << i18n("Legendre function Q_0"); m_functionsNames << i18n("Legendre function Q_1"); m_functionsNames << i18n("Legendre function Q_l"); m_functionsNames << i18n("Associated Legendre polynomial"); m_functionsNames << i18n("Normalized associated Legendre polynomial"); m_functionsNames << i18n("Irregular spherical conical function P^1/2"); m_functionsNames << i18n("Regular spherical conical function P^(-1/2)"); m_functionsNames << i18n("Conical function P^0"); m_functionsNames << i18n("Conical function P^1"); m_functionsNames << i18n("Regular spherical conical function P^(-1/2-l)"); m_functionsNames << i18n("Regular cylindrical conical function P^(-m)"); m_functionsNames << i18n("Zeroth radial eigenfunction of the Laplacian on the 3-dimensional hyperbolic space"); m_functionsNames << i18n("First radial eigenfunction of the Laplacian on the 3-dimensional hyperbolic space"); m_functionsNames << i18n("l-th radial eigenfunction of the Laplacian on the 3-dimensional hyperbolic space"); index++; for (int i = 0; i < 18; i++) m_functionsGroupIndex << index; // Logarithm and Related Functions m_functionsNames << i18n("Logarithm"); m_functionsNames << i18n("Logarithm of the magnitude"); m_functionsNames << QString("log(1+x)"); m_functionsNames << QString("log(1+x) - x"); index++; for (int i = 0; i < 4; i++) m_functionsGroupIndex << index; // Power Function m_functionsNames << i18n("x^n for integer n with an error estimate"); index++; m_functionsGroupIndex << index; // Psi (Digamma) Function m_functionsNames << i18n("Digamma function for positive integer n"); m_functionsNames << i18n("Digamma function"); m_functionsNames << i18n("Real part of the digamma function on the line 1+i y"); m_functionsNames << i18n("Trigamma function psi' for positive integer n"); m_functionsNames << i18n("Trigamma function psi'"); m_functionsNames << i18n("Polygamma function psi^(n)"); index++; for (int i = 0; i < 6; i++) m_functionsGroupIndex << index; // Synchrotron Functions m_functionsNames << i18n("First synchrotron function"); m_functionsNames << i18n("Second synchrotron function"); index++; for (int i = 0; i < 2; i++) m_functionsGroupIndex << index; // Transport Functions m_functionsNames << i18n("Transport function"); m_functionsNames << i18n("Transport function"); m_functionsNames << i18n("Transport function"); m_functionsNames << i18n("Transport function"); index++; for (int i = 0; i < 4; i++) m_functionsGroupIndex << index; // Trigonometric Functions m_functionsNames << i18n("Sine"); m_functionsNames << i18n("Cosine"); m_functionsNames << i18n("Tangent"); m_functionsNames << i18n("Inverse sine"); m_functionsNames << i18n("Inverse cosine"); m_functionsNames << i18n("Inverse tangent"); m_functionsNames << i18n("Inverse tangent using sign"); m_functionsNames << i18n("Hyperbolic sine"); m_functionsNames << i18n("Hyperbolic cosine"); m_functionsNames << i18n("Hyperbolic tangent"); m_functionsNames << i18n("Inverse hyperbolic cosine"); m_functionsNames << i18n("Inverse hyperbolic sine"); m_functionsNames << i18n("Inverse hyperbolic tangent"); m_functionsNames << i18n("Secant"); m_functionsNames << i18n("Cosecant"); m_functionsNames << i18n("Cotangent"); m_functionsNames << i18n("Inverse secant"); m_functionsNames << i18n("Inverse cosecant"); m_functionsNames << i18n("Inverse cotangent"); m_functionsNames << i18n("Hyperbolic secant"); m_functionsNames << i18n("Hyperbolic cosecant"); m_functionsNames << i18n("Hyperbolic cotangent"); m_functionsNames << i18n("Inverse hyperbolic secant"); m_functionsNames << i18n("Inverse hyperbolic cosecant"); m_functionsNames << i18n("Inverse hyperbolic cotangent"); m_functionsNames << i18n("Sinc function sin(x)/x"); m_functionsNames << QString("log(sinh(x))"); m_functionsNames << QString("log(cosh(x))"); m_functionsNames << i18n("Hypotenuse function"); m_functionsNames << i18n("Three component hypotenuse function"); m_functionsNames << i18n("restrict to [-pi,pi]"); m_functionsNames << i18n("restrict to [0,2 pi]"); index++; for (int i = 0; i < 32; i++) m_functionsGroupIndex << index; // Zeta Functions m_functionsNames << i18n("Riemann zeta function for integer n"); m_functionsNames << i18n("Riemann zeta function"); m_functionsNames << i18n("zeta(n)-1 for integer n"); m_functionsNames << i18n("zeta(x)-1"); m_functionsNames << i18n("Hurwitz zeta function"); m_functionsNames << i18n("Eta function for integer n"); m_functionsNames << i18n("Eta function"); index++; for (int i = 0; i < 7; i++) m_functionsGroupIndex << index; // GSL Random Number Distributions: see https://www.gnu.org/software/gsl/doc/html/randist.html // Gaussian Distribution m_functionsNames << i18n("Probability density for a Gaussian distribution"); m_functionsNames << i18n("Probability density for a unit Gaussian distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); m_functionsNames << i18n("Cumulative unit distribution function P"); m_functionsNames << i18n("Cumulative unit distribution function Q"); m_functionsNames << i18n("Inverse cumulative unit distribution function P"); m_functionsNames << i18n("Inverse cumulative unit distribution function Q"); m_functionsNames << i18n("Probability density for Gaussian tail distribution"); m_functionsNames << i18n("Probability density for unit Gaussian tail distribution"); m_functionsNames << i18n("Probability density for a bivariate Gaussian distribution"); index++; for (int i = 0; i < 13; i++) m_functionsGroupIndex << index; // Exponential Distribution m_functionsNames << i18n("Probability density for an exponential distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Laplace Distribution m_functionsNames << i18n("Probability density for a Laplace distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Exponential Power Distribution m_functionsNames << i18n("Probability density for an exponential power distribution"); m_functionsNames << i18n("cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); index++; for (int i = 0; i < 3; i++) m_functionsGroupIndex << index; // Cauchy Distribution m_functionsNames << i18n("Probability density for a Cauchy distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Rayleigh Distribution m_functionsNames << i18n("Probability density for a Rayleigh distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); m_functionsNames << i18n("Probability density for a Rayleigh tail distribution"); index++; for (int i = 0; i < 6; i++) m_functionsGroupIndex << index; // Landau Distribution m_functionsNames << i18n("Probability density for a Landau distribution"); index++; m_functionsGroupIndex << index; // Gamma Distribution m_functionsNames << i18n("Probability density for a gamma distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Flat (Uniform) Distribution m_functionsNames << i18n("Probability density for a uniform distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Lognormal Distribution m_functionsNames << i18n("Probability density for a lognormal distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Chi-squared Distribution m_functionsNames << i18n("Probability density for a chi squared distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // F-distribution m_functionsNames << i18n("Probability density for a F-distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // t-distribution m_functionsNames << i18n("Probability density for a t-distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Beta Distribution m_functionsNames << i18n("Probability density for a beta distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Logistic Distribution m_functionsNames << i18n("Probability density for a logistic distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Pareto Distribution m_functionsNames << i18n("Probability density for a Pareto distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Weibull Distribution m_functionsNames << i18n("Probability density for a Weibull distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 5; i++) m_functionsGroupIndex << index; // Gumbel Distribution m_functionsNames << i18n("Probability density for a Type-1 Gumbel distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); m_functionsNames << i18n("Probability density for a Type-2 Gumbel distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Inverse cumulative distribution function P"); m_functionsNames << i18n("Inverse cumulative distribution function Q"); index++; for (int i = 0; i < 10; i++) m_functionsGroupIndex << index; // Poisson Distribution m_functionsNames << i18n("Probability density for a Poisson distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); index++; for (int i = 0; i < 3; i++) m_functionsGroupIndex << index; // Bernoulli Distribution m_functionsNames << i18n("Probability density for a Bernoulli distribution"); index++; m_functionsGroupIndex << index; // Binomial Distribution m_functionsNames << i18n("Probability density for a binomial distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); m_functionsNames << i18n("Probability density for a negative binomial distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); index++; for (int i = 0; i < 6; i++) m_functionsGroupIndex << index; // Pascal Distribution m_functionsNames << i18n("Probability density for a Pascal distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); index++; for (int i = 0; i < 3; i++) m_functionsGroupIndex << index; // Geometric Distribution m_functionsNames << i18n("Probability density for a geometric distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); index++; for (int i = 0; i < 3; i++) m_functionsGroupIndex << index; // Hypergeometric Distribution m_functionsNames << i18n("Probability density for a hypergeometric distribution"); m_functionsNames << i18n("Cumulative distribution function P"); m_functionsNames << i18n("Cumulative distribution function Q"); index++; for (int i = 0; i < 3; i++) m_functionsGroupIndex << index; // Logarithmic Distribution m_functionsNames << i18n("Probability density for a logarithmic distribution"); index++; m_functionsGroupIndex << index; } //TODO: decide whether we want to have i18n here in the backend part of the code void ExpressionParser::initConstants() { for (int i = 0; _constants[i].name != nullptr; i++) m_constants << _constants[i].name; //groups m_constantsGroups << i18n("Mathematical constants"); m_constantsGroups << i18n("Fundamental constants"); m_constantsGroups << i18n("Astronomy and Astrophysics"); m_constantsGroups << i18n("Atomic and Nuclear Physics"); m_constantsGroups << i18n("Measurement of Time"); m_constantsGroups << i18n("Imperial Units"); m_constantsGroups << i18n("Speed and Nautical Units"); m_constantsGroups << i18n("Printers Units"); m_constantsGroups << i18n("Volume, Area and Length"); m_constantsGroups << i18n("Mass and Weight"); m_constantsGroups << i18n("Thermal Energy and Power"); m_constantsGroups << i18n("Pressure"); m_constantsGroups << i18n("Viscosity"); m_constantsGroups << i18n("Light and Illumination"); m_constantsGroups << i18n("Radioactivity"); m_constantsGroups << i18n("Force and Energy"); //Mathematical constants m_constantsNames << i18n("Base of exponentials"); m_constantsValues << QString::number(M_E,'g',15); m_constantsUnits << QString(); m_constantsNames << i18n("Pi"); m_constantsValues << QString::number(M_PI,'g',15); m_constantsUnits << QString(); m_constantsNames << i18n("Euler's constant"); m_constantsValues << QString::number(M_EULER,'g',15); m_constantsUnits << QString(); for (int i = 0; i < 3; i++) m_constantsGroupIndex << 0; //Fundamental constants m_constantsNames << i18n("Speed of light"); m_constantsValues << QString::number(GSL_CONST_MKSA_SPEED_OF_LIGHT,'g',15); m_constantsUnits << "m / s"; m_constantsNames << i18n("Vacuum permeability"); m_constantsValues << QString::number(GSL_CONST_MKSA_VACUUM_PERMEABILITY,'g',15); m_constantsUnits << "kg m / A^2 s^2"; m_constantsNames << i18n("Vacuum permittivity"); m_constantsValues << QString::number(GSL_CONST_MKSA_VACUUM_PERMITTIVITY,'g',15); m_constantsUnits << "A^2 s^4 / kg m^3"; m_constantsNames << i18n("Planck constant"); m_constantsValues << QString::number(GSL_CONST_MKSA_PLANCKS_CONSTANT_H,'g',15); m_constantsUnits << "kg m^2 / s"; m_constantsNames << i18n("Reduced Planck constant"); m_constantsValues << QString::number(GSL_CONST_MKSA_PLANCKS_CONSTANT_HBAR,'g',15); m_constantsUnits << "kg m^2 / s"; m_constantsNames << i18n("Avogadro constant"); m_constantsValues << QString::number(GSL_CONST_NUM_AVOGADRO,'g',15); m_constantsUnits << "1 / mol"; m_constantsNames << i18n("Faraday"); m_constantsValues << QString::number(GSL_CONST_MKSA_FARADAY,'g',15); m_constantsUnits << "A s / mol"; m_constantsNames << i18n("Boltzmann constant"); m_constantsValues << QString::number(GSL_CONST_MKSA_BOLTZMANN,'g',15); m_constantsUnits << "kg m^2 / K s^2"; m_constantsNames << i18n("Molar gas"); m_constantsValues << QString::number(GSL_CONST_MKSA_MOLAR_GAS,'g',15); m_constantsUnits << "kg m^2 / K mol s^2"; m_constantsNames << i18n("Standard gas volume"); m_constantsValues << QString::number(GSL_CONST_MKSA_STANDARD_GAS_VOLUME,'g',15); m_constantsUnits << "m^3 / mol"; m_constantsNames << i18n("Stefan-Boltzmann constant"); m_constantsValues << QString::number(GSL_CONST_MKSA_STEFAN_BOLTZMANN_CONSTANT,'g',15); m_constantsUnits << "kg / K^4 s^3"; m_constantsNames << i18n("Gauss"); m_constantsValues << QString::number(GSL_CONST_MKSA_GAUSS,'g',15); m_constantsUnits << "kg / A s^2"; for (int i = 0; i < 12; i++) m_constantsGroupIndex << 1; // Astronomy and Astrophysics m_constantsNames << i18n("Astronomical unit"); m_constantsValues << QString::number(GSL_CONST_MKSA_ASTRONOMICAL_UNIT,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Gravitational constant"); m_constantsValues << QString::number(GSL_CONST_MKSA_GRAVITATIONAL_CONSTANT,'g',15); m_constantsUnits << "m^3 / kg s^2"; m_constantsNames << i18n("Light year"); m_constantsValues << QString::number(GSL_CONST_MKSA_LIGHT_YEAR,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Parsec"); m_constantsValues << QString::number(GSL_CONST_MKSA_PARSEC,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Gravitational acceleration"); m_constantsValues << QString::number(GSL_CONST_MKSA_GRAV_ACCEL,'g',15); m_constantsUnits << "m / s^2"; m_constantsNames << i18n("Solar mass"); m_constantsValues << QString::number(GSL_CONST_MKSA_SOLAR_MASS,'g',15); m_constantsUnits << "kg"; for (int i = 0; i < 6; i++) m_constantsGroupIndex << 2; // Atomic and Nuclear Physics; m_constantsNames << i18n("Charge of the electron"); m_constantsValues << QString::number(GSL_CONST_MKSA_ELECTRON_CHARGE,'g',15); m_constantsUnits << "A s"; m_constantsNames << i18n("Energy of 1 electron volt"); m_constantsValues << QString::number(GSL_CONST_MKSA_ELECTRON_VOLT,'g',15); m_constantsUnits << "kg m^2 / s^2"; m_constantsNames << i18n("Unified atomic mass"); m_constantsValues << QString::number(GSL_CONST_MKSA_UNIFIED_ATOMIC_MASS,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of the electron"); m_constantsValues << QString::number(GSL_CONST_MKSA_MASS_ELECTRON,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of the muon"); m_constantsValues << QString::number(GSL_CONST_MKSA_MASS_MUON,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of the proton"); m_constantsValues << QString::number(GSL_CONST_MKSA_MASS_PROTON,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of the neutron"); m_constantsValues << QString::number(GSL_CONST_MKSA_MASS_NEUTRON,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Electromagnetic fine structure constant"); m_constantsValues << QString::number(GSL_CONST_NUM_FINE_STRUCTURE,'g',15); m_constantsUnits << QString(); m_constantsNames << i18n("Rydberg constant"); m_constantsValues << QString::number(GSL_CONST_MKSA_RYDBERG,'g',15); m_constantsUnits << "kg m^2 / s^2"; m_constantsNames << i18n("Bohr radius"); m_constantsValues << QString::number(GSL_CONST_MKSA_BOHR_RADIUS,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Length of 1 angstrom"); m_constantsValues << QString::number(GSL_CONST_MKSA_ANGSTROM,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Area of 1 barn"); m_constantsValues << QString::number(GSL_CONST_MKSA_BARN,'g',15); m_constantsUnits << "m^2"; m_constantsNames << i18n("Bohr Magneton"); m_constantsValues << QString::number(GSL_CONST_MKSA_BOHR_MAGNETON,'g',15); m_constantsUnits << "A m^2"; m_constantsNames << i18n("Nuclear Magneton"); m_constantsValues << QString::number(GSL_CONST_MKSA_NUCLEAR_MAGNETON,'g',15); m_constantsUnits << "A m^2"; m_constantsNames << i18n("Magnetic moment of the electron [absolute value]"); m_constantsValues << QString::number(GSL_CONST_MKSA_ELECTRON_MAGNETIC_MOMENT,'g',15); m_constantsUnits << "A m^2"; m_constantsNames << i18n("Magnetic moment of the proton"); m_constantsValues << QString::number(GSL_CONST_MKSA_PROTON_MAGNETIC_MOMENT,'g',15); m_constantsUnits << "A m^2"; m_constantsNames << i18n("Thomson cross section"); m_constantsValues << QString::number(GSL_CONST_MKSA_THOMSON_CROSS_SECTION,'g',15); m_constantsUnits << "m^2"; m_constantsNames << i18n("Electric dipole moment of 1 Debye"); m_constantsValues << QString::number(GSL_CONST_MKSA_DEBYE,'g',15); m_constantsUnits << "A s^2 / m^2"; for (int i = 0; i < 18; i++) m_constantsGroupIndex << 3; // Measurement of Time m_constantsNames << i18n("Number of seconds in 1 minute"); m_constantsValues << QString::number(GSL_CONST_MKSA_MINUTE,'g',15); m_constantsUnits << "s"; m_constantsNames << i18n("Number of seconds in 1 hour"); m_constantsValues << QString::number(GSL_CONST_MKSA_HOUR,'g',15); m_constantsUnits << "s"; m_constantsNames << i18n("Number of seconds in 1 day"); m_constantsValues << QString::number(GSL_CONST_MKSA_DAY,'g',15); m_constantsUnits << "s"; m_constantsNames << i18n("Number of seconds in 1 week"); m_constantsValues << QString::number(GSL_CONST_MKSA_WEEK,'g',15); m_constantsUnits << "s"; for (int i = 0; i < 4; i++) m_constantsGroupIndex << 4; // Imperial Units m_constantsNames << i18n("Length of 1 inch"); m_constantsValues << QString::number(GSL_CONST_MKSA_INCH,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Length of 1 foot"); m_constantsValues << QString::number(GSL_CONST_MKSA_FOOT,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Length of 1 yard"); m_constantsValues << QString::number(GSL_CONST_MKSA_YARD,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Length of 1 mile"); m_constantsValues << QString::number(GSL_CONST_MKSA_MILE,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Length of 1/1000th of an inch"); m_constantsValues << QString::number(GSL_CONST_MKSA_MIL,'g',15); m_constantsUnits << "m"; for (int i = 0; i < 5; i++) m_constantsGroupIndex << 5; // Speed and Nautical Units m_constantsNames << i18n("Speed of 1 kilometer per hour"); m_constantsValues << QString::number(GSL_CONST_MKSA_KILOMETERS_PER_HOUR,'g',15); m_constantsUnits << "m / s"; m_constantsNames << i18n("Speed of 1 mile per hour"); m_constantsValues << QString::number(GSL_CONST_MKSA_MILES_PER_HOUR,'g',15); m_constantsUnits << "m / s"; m_constantsNames << i18n("Length of 1 nautical mile"); m_constantsValues << QString::number(GSL_CONST_MKSA_NAUTICAL_MILE,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Length of 1 fathom"); m_constantsValues << QString::number(GSL_CONST_MKSA_FATHOM,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Speed of 1 knot"); m_constantsValues << QString::number(GSL_CONST_MKSA_KNOT,'g',15); m_constantsUnits << "m / s"; for (int i = 0; i < 5; i++) m_constantsGroupIndex << 6; // Printers Units m_constantsNames << i18n("length of 1 printer's point [1/72 inch]"); m_constantsValues << QString::number(GSL_CONST_MKSA_POINT,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("length of 1 TeX point [1/72.27 inch]"); m_constantsValues << QString::number(GSL_CONST_MKSA_TEXPOINT,'g',15); m_constantsUnits << "m"; for (int i = 0; i < 2; i++) m_constantsGroupIndex << 7; // Volume, Area and Length m_constantsNames << i18n("Length of 1 micron"); m_constantsValues << QString::number(GSL_CONST_MKSA_MICRON,'g',15); m_constantsUnits << "m"; m_constantsNames << i18n("Area of 1 hectare"); m_constantsValues << QString::number(GSL_CONST_MKSA_HECTARE,'g',15); m_constantsUnits << "m^2"; m_constantsNames << i18n("Area of 1 acre"); m_constantsValues << QString::number(GSL_CONST_MKSA_ACRE,'g',15); m_constantsUnits << "m^2"; m_constantsNames << i18n("Volume of 1 liter"); m_constantsValues << QString::number(GSL_CONST_MKSA_LITER,'g',15); m_constantsUnits << "m^3"; m_constantsNames << i18n("Volume of 1 US gallon"); m_constantsValues << QString::number(GSL_CONST_MKSA_US_GALLON,'g',15); m_constantsUnits << "m^3"; m_constantsNames << i18n("Volume of 1 Canadian gallon"); m_constantsValues << QString::number(GSL_CONST_MKSA_CANADIAN_GALLON,'g',15); m_constantsUnits << "m^3"; m_constantsNames << i18n("Volume of 1 UK gallon"); m_constantsValues << QString::number(GSL_CONST_MKSA_UK_GALLON,'g',15); m_constantsUnits << "m^3"; m_constantsNames << i18n("Volume of 1 quart"); m_constantsValues << QString::number(GSL_CONST_MKSA_QUART,'g',15); m_constantsUnits << "m^3"; m_constantsNames << i18n("Volume of 1 pint"); m_constantsValues << QString::number(GSL_CONST_MKSA_PINT,'g',15); m_constantsUnits << "m^3"; for (int i = 0; i < 9; i++) m_constantsGroupIndex << 8; // Mass and Weight m_constantsNames << i18n("Mass of 1 pound"); m_constantsValues << QString::number(GSL_CONST_MKSA_POUND_MASS,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of 1 ounce"); m_constantsValues << QString::number(GSL_CONST_MKSA_OUNCE_MASS,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of 1 ton"); m_constantsValues << QString::number(GSL_CONST_MKSA_TON,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of 1 metric ton [1000 kg]"); m_constantsValues << QString::number(GSL_CONST_MKSA_METRIC_TON,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of 1 UK ton"); m_constantsValues << QString::number(GSL_CONST_MKSA_UK_TON,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of 1 troy ounce"); m_constantsValues << QString::number(GSL_CONST_MKSA_TROY_OUNCE,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Mass of 1 carat"); m_constantsValues << QString::number(GSL_CONST_MKSA_CARAT,'g',15); m_constantsUnits << "kg"; m_constantsNames << i18n("Force of 1 gram weight"); m_constantsValues << QString::number(GSL_CONST_MKSA_GRAM_FORCE,'g',15); m_constantsUnits << "kg m / s^2"; m_constantsNames << i18n("Force of 1 pound weight"); m_constantsValues << QString::number(GSL_CONST_MKSA_POUND_FORCE,'g',15); m_constantsUnits << "kg m / s^2"; m_constantsNames << i18n("Force of 1 kilopound weight"); m_constantsValues << QString::number(GSL_CONST_MKSA_KILOPOUND_FORCE,'g',15); m_constantsUnits << "kg m / s^2"; m_constantsNames << i18n("Force of 1 poundal"); m_constantsValues << QString::number(GSL_CONST_MKSA_POUNDAL,'g',15); m_constantsUnits << "kg m / s^2"; for (int i = 0; i < 11; i++) m_constantsGroupIndex << 9; // Thermal Energy and Power m_constantsNames << i18n("Energy of 1 calorie"); m_constantsValues << QString::number(GSL_CONST_MKSA_CALORIE,'g',15); m_constantsUnits << "kg m^2 / s^2"; m_constantsNames << i18n("Energy of 1 British Thermal Unit"); m_constantsValues << QString::number(GSL_CONST_MKSA_BTU,'g',15); m_constantsUnits << "kg m^2 / s^2"; m_constantsNames << i18n("Energy of 1 Therm"); m_constantsValues << QString::number(GSL_CONST_MKSA_THERM,'g',15); m_constantsUnits << "kg m^2 / s^2"; m_constantsNames << i18n("Power of 1 horsepower"); m_constantsValues << QString::number(GSL_CONST_MKSA_HORSEPOWER,'g',15); m_constantsUnits << "kg m^2 / s^3"; for (int i = 0; i < 4; i++) m_constantsGroupIndex << 10; // Pressure m_constantsNames << i18n("Pressure of 1 bar"); m_constantsValues << QString::number(GSL_CONST_MKSA_BAR,'g',15); m_constantsUnits << "kg / m s^2"; m_constantsNames << i18n("Pressure of 1 standard atmosphere"); m_constantsValues << QString::number(GSL_CONST_MKSA_STD_ATMOSPHERE,'g',15); m_constantsUnits << "kg / m s^2"; m_constantsNames << i18n("Pressure of 1 torr"); m_constantsValues << QString::number(GSL_CONST_MKSA_TORR,'g',15); m_constantsUnits << "kg / m s^2"; m_constantsNames << i18n("Pressure of 1 meter of mercury"); m_constantsValues << QString::number(GSL_CONST_MKSA_METER_OF_MERCURY,'g',15); m_constantsUnits << "kg / m s^2"; m_constantsNames << i18n("Pressure of 1 inch of mercury"); m_constantsValues << QString::number(GSL_CONST_MKSA_INCH_OF_MERCURY,'g',15); m_constantsUnits << "kg / m s^2"; m_constantsNames << i18n("Pressure of 1 inch of water"); m_constantsValues << QString::number(GSL_CONST_MKSA_INCH_OF_WATER,'g',15); m_constantsUnits << "kg / m s^2"; m_constantsNames << i18n("Pressure of 1 pound per square inch"); m_constantsValues << QString::number(GSL_CONST_MKSA_PSI,'g',15); m_constantsUnits << "kg / m s^2"; for (int i = 0; i < 7; i++) m_constantsGroupIndex << 11; // Viscosity m_constantsNames << i18n("Dynamic viscosity of 1 poise"); m_constantsValues << QString::number(GSL_CONST_MKSA_POISE,'g',15); m_constantsUnits << "kg / m s"; m_constantsNames << i18n("Kinematic viscosity of 1 stokes"); m_constantsValues << QString::number(GSL_CONST_MKSA_STOKES,'g',15); m_constantsUnits << "m^2 / s"; for (int i = 0; i < 2; i++) m_constantsGroupIndex << 12; // Light and Illumination m_constantsNames << i18n("Luminance of 1 stilb"); m_constantsValues << QString::number(GSL_CONST_MKSA_STILB,'g',15); m_constantsUnits << "cd / m^2"; m_constantsNames << i18n("Luminous flux of 1 lumen"); m_constantsValues << QString::number(GSL_CONST_MKSA_LUMEN,'g',15); m_constantsUnits << "cd sr"; m_constantsNames << i18n("Illuminance of 1 lux"); m_constantsValues << QString::number(GSL_CONST_MKSA_LUX,'g',15); m_constantsUnits << "cd sr / m^2"; m_constantsNames << i18n("Illuminance of 1 phot"); m_constantsValues << QString::number(GSL_CONST_MKSA_PHOT,'g',15); m_constantsUnits << "cd sr / m^2"; m_constantsNames << i18n("Illuminance of 1 footcandle"); m_constantsValues << QString::number(GSL_CONST_MKSA_FOOTCANDLE,'g',15); m_constantsUnits << "cd sr / m^2"; m_constantsNames << i18n("Luminance of 1 lambert"); m_constantsValues << QString::number(GSL_CONST_MKSA_LAMBERT,'g',15); m_constantsUnits << "cd sr / m^2"; m_constantsNames << i18n("Luminance of 1 footlambert"); m_constantsValues << QString::number(GSL_CONST_MKSA_FOOTLAMBERT,'g',15); m_constantsUnits << "cd sr / m^2"; for (int i = 0; i < 7; i++) m_constantsGroupIndex << 13; // Radioactivity m_constantsNames << i18n("Activity of 1 curie"); m_constantsValues << QString::number(GSL_CONST_MKSA_CURIE,'g',15); m_constantsUnits << "1 / s"; m_constantsNames << i18n("Exposure of 1 roentgen"); m_constantsValues << QString::number(GSL_CONST_MKSA_ROENTGEN,'g',15); m_constantsUnits << "A s / kg"; m_constantsNames << i18n("Absorbed dose of 1 rad"); m_constantsValues << QString::number(GSL_CONST_MKSA_RAD,'g',15); m_constantsUnits << "m^2 / s^2"; for (int i = 0; i < 3; i++) m_constantsGroupIndex << 14; // Force and Energy m_constantsNames << i18n("SI unit of force"); m_constantsValues << QString::number(GSL_CONST_MKSA_NEWTON,'g',15); m_constantsUnits << "kg m / s^2"; m_constantsNames << i18n("Force of 1 Dyne"); m_constantsValues << QString::number(GSL_CONST_MKSA_DYNE,'g',15); m_constantsUnits << "kg m / s^2"; m_constantsNames << i18n("SI unit of energy"); m_constantsValues << QString::number(GSL_CONST_MKSA_JOULE,'g',15); m_constantsUnits << "kg m^2 / s^2"; m_constantsNames << i18n("Energy 1 erg"); m_constantsValues << QString::number(GSL_CONST_MKSA_ERG,'g',15); m_constantsUnits << "kg m^2 / s^2"; for (int i = 0; i < 4; i++) m_constantsGroupIndex << 15; } ExpressionParser::~ExpressionParser() { delete_table(); } ExpressionParser* ExpressionParser::getInstance() { if (!instance) instance = new ExpressionParser(); return instance; } const QStringList& ExpressionParser::functions() { return m_functions; } const QStringList& ExpressionParser::functionsGroups() { return m_functionsGroups; } const QStringList& ExpressionParser::functionsNames() { return m_functionsNames; } const QVector& ExpressionParser::functionsGroupIndices() { return m_functionsGroupIndex; } const QStringList& ExpressionParser::constants() { return m_constants; } const QStringList& ExpressionParser::constantsGroups() { return m_constantsGroups; } const QStringList& ExpressionParser::constantsNames() { return m_constantsNames; } const QStringList& ExpressionParser::constantsValues() { return m_constantsValues; } const QStringList& ExpressionParser::constantsUnits() { return m_constantsUnits; } const QVector& ExpressionParser::constantsGroupIndices() { return m_constantsGroupIndex; } bool ExpressionParser::isValid(const QString& expr, const QStringList& vars) { for (int i = 0; i < vars.size(); ++i) { QByteArray varba = vars.at(i).toLatin1(); assign_variable(varba.constData(), 0); } QByteArray funcba = expr.toLatin1(); const char* data = funcba.constData(); gsl_set_error_handler_off(); parse(data); return !(parse_errors() > 0); } QStringList ExpressionParser::getParameter(const QString& expr, const QStringList& vars) { DEBUG("ExpressionParser::getParameter()"); QDEBUG("variables:" << vars); QStringList parameters; QStringList strings = expr.split(QRegularExpression(QStringLiteral("\\W+")), QString::SkipEmptyParts); QDEBUG("found strings:" << strings); // RE for any number #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) const QRegularExpression re(QRegularExpression::anchoredPattern(QStringLiteral("[0-9]*"))); #else const QRegularExpression re("\\A(?:" + QStringLiteral("[0-9]*") + ")\\z"); #endif for (int i = 0; i < strings.size(); ++i) { QDEBUG(strings[i] << ':' << constants().indexOf(strings[i]) << ' ' << functions().indexOf(strings[i]) << ' ' << vars.indexOf(strings[i]) << ' ' << re.match(strings[i]).hasMatch()); // check if token is not a known constant/function/variable or number if (constants().indexOf(strings[i]) == -1 && functions().indexOf(strings[i]) == -1 && vars.indexOf(strings[i]) == -1 && re.match(strings[i]).hasMatch() == false) parameters << strings[i]; } parameters.removeDuplicates(); QDEBUG("parameters found:" << parameters); return parameters; } /* * Evaluate cartesian expression returning true on success and false if parsing fails */ bool ExpressionParser::evaluateCartesian(const QString& expr, const QString& min, const QString& max, int count, QVector* xVector, QVector* yVector, const QStringList& paramNames, const QVector& paramValues) { + DEBUG("ExpressionParser::evaluateCartesian() 1") QByteArray xminba = min.toLatin1(); const double xMin = parse(xminba.constData()); QByteArray xmaxba = max.toLatin1(); const double xMax = parse(xmaxba.constData()); const double step = (xMax - xMin)/(double)(count - 1); QByteArray funcba = expr.toLatin1(); const char* func = funcba.constData(); gsl_set_error_handler_off(); for (int i = 0; i < paramNames.size(); ++i) { QByteArray paramba = paramNames.at(i).toLatin1(); assign_variable(paramba.constData(), paramValues.at(i)); } for (int i = 0; i < count; i++) { const double x = xMin + step * i; assign_variable("x", x); const double y = parse(func); if (parse_errors() > 0) return false; (*xVector)[i] = x; (*yVector)[i] = y; } return true; } bool ExpressionParser::evaluateCartesian(const QString& expr, const QString& min, const QString& max, int count, QVector* xVector, QVector* yVector) { + DEBUG("ExpressionParser::evaluateCartesian() 2") QByteArray xminba = min.toLatin1(); const double xMin = parse(xminba.constData()); QByteArray xmaxba = max.toLatin1(); const double xMax = parse(xmaxba.constData()); const double step = (xMax - xMin)/(double)(count - 1); QByteArray funcba = expr.toLatin1(); const char* func = funcba.constData(); gsl_set_error_handler_off(); for (int i = 0; i < count; i++) { const double x = xMin + step*i; assign_variable("x", x); const double y = parse(func); if (parse_errors() > 0) return false; (*xVector)[i] = x; (*yVector)[i] = y; } return true; } bool ExpressionParser::evaluateCartesian(const QString& expr, QVector* xVector, QVector* yVector) { + DEBUG("ExpressionParser::evaluateCartesian() 3") QByteArray funcba = expr.toLatin1(); const char* func = funcba.constData(); gsl_set_error_handler_off(); for (int i = 0; i < xVector->count(); i++) { const double x = xVector->at(i); assign_variable("x", x); const double y = parse(func); if (parse_errors() > 0) return false; (*yVector)[i] = y; } return true; } bool ExpressionParser::evaluateCartesian(const QString& expr, QVector* xVector, QVector* yVector, const QStringList& paramNames, const QVector& paramValues) { + DEBUG("ExpressionParser::evaluateCartesian() 4") QByteArray funcba = expr.toLatin1(); const char* func = funcba.constData(); gsl_set_error_handler_off(); for (int i = 0; i < paramNames.size(); ++i) { QByteArray paramba = paramNames.at(i).toLatin1(); assign_variable(paramba.constData(), paramValues.at(i)); } for (int i = 0; i < xVector->count(); i++) { const double x = xVector->at(i); assign_variable("x", x); const double y = parse(func); if (parse_errors() > 0) return false; (*yVector)[i] = y; } return true; } /*! evaluates multivariate function y=f(x_1, x_2, ...). Variable names (x_1, x_2, ...) are stored in \c vars. Data is stored in \c dataVectors. */ bool ExpressionParser::evaluateCartesian(const QString& expr, const QStringList& vars, const QVector*>& xVectors, QVector* yVector) { + DEBUG("ExpressionParser::evaluateCartesian() 4") Q_ASSERT(vars.size() == xVectors.size()); QByteArray funcba = expr.toLatin1(); const char* func = funcba.constData(); gsl_set_error_handler_off(); //determine the minimal size of involved vectors double minSize = INFINITY; for (auto* xVector : xVectors) { if (xVector->size() < minSize) minSize = xVector->size(); } if (yVector->size() < minSize) minSize = yVector->size(); for (int i = 0; i < minSize; i++) { for (int n = 0; n < vars.size(); ++n) { const QString& varName = vars.at(n); const double varValue = xVectors.at(n)->at(i); QByteArray varba = varName.toLatin1(); assign_variable(varba.constData(), varValue); } + //TODO DEBUG("BEFORE CRASH") const double y = parse(func); + //DEBUG("AFTER CRASH") if (parse_errors() > 0) return false; (*yVector)[i] = y; } //in case the y-vector is longer than the x-vector(s), set all elements that were not calculated to NAN for (int i = minSize; i < yVector->size(); ++i) (*yVector)[i] = NAN; return true; } bool ExpressionParser::evaluatePolar(const QString& expr, const QString& min, const QString& max, int count, QVector* xVector, QVector* yVector) { QByteArray minba = min.toLatin1(); const double minValue = parse(minba.constData()); QByteArray maxba = max.toLatin1(); const double maxValue = parse(maxba.constData()); const double step = (maxValue - minValue)/(double)(count - 1); QByteArray funcba = expr.toLatin1(); const char* func = funcba.constData(); gsl_set_error_handler_off(); for (int i = 0; i < count; i++) { const double phi = minValue + step * i; assign_variable("phi", phi); const double r = parse(func); if (parse_errors() > 0) return false; (*xVector)[i] = r*cos(phi); (*yVector)[i] = r*sin(phi); } return true; } bool ExpressionParser::evaluateParametric(const QString& expr1, const QString& expr2, const QString& min, const QString& max, int count, QVector* xVector, QVector* yVector) { QByteArray minba = min.toLatin1(); const double minValue = parse(minba.constData()); QByteArray maxba = max.toLatin1(); const double maxValue = parse(maxba.constData()); const double step = (maxValue - minValue)/(double)(count - 1); QByteArray xfuncba = expr1.toLatin1(); const char* xFunc = xfuncba.constData(); QByteArray yfuncba = expr2.toLatin1(); const char* yFunc = yfuncba.constData(); gsl_set_error_handler_off(); for (int i = 0; i < count; i++) { const double t = minValue + step*i; assign_variable("t", t); const double x = parse(xFunc); if (parse_errors() > 0) return false; (*xVector)[i] = x; const double y = parse(yFunc); if (parse_errors() > 0) return false; (*yVector)[i] = y; } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp index 7eddc8563..18beb6f35 100644 --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp @@ -1,2474 +1,2475 @@ /*************************************************************************** File : XYFitCurve.cpp Project : LabPlot Description : A xy-curve defined by a fit model -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2020 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * 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 2 of the License, 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; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFitCurve \brief A xy-curve defined by a fit model \ingroup worksheet */ #include "XYFitCurve.h" #include "XYFitCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" #include "backend/gsl/ExpressionParser.h" extern "C" { #include #include #include #include #include #include #include "backend/gsl/parser.h" #include "backend/nsl/nsl_sf_stats.h" #include "backend/nsl/nsl_stats.h" } #include #include #include #include XYFitCurve::XYFitCurve(const QString& name) : XYAnalysisCurve(name, new XYFitCurvePrivate(this), AspectType::XYFitCurve) { } XYFitCurve::XYFitCurve(const QString& name, XYFitCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYFitCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYFitCurve::~XYFitCurve() = default; void XYFitCurve::recalculate() { Q_D(XYFitCurve); d->recalculate(); } void XYFitCurve::evaluate(bool preview) { Q_D(XYFitCurve); d->evaluate(preview); } void XYFitCurve::initStartValues(const XYCurve* curve) { Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; initStartValues(fitData, curve); } void XYFitCurve::initStartValues(XYFitCurve::FitData& fitData, const XYCurve* curve) { DEBUG("XYFitCurve::initStartValues()"); if (!curve) { DEBUG(" no curve given"); return; } const Column* tmpXDataColumn = dynamic_cast(curve->xColumn()); const Column* tmpYDataColumn = dynamic_cast(curve->yColumn()); if (!tmpXDataColumn || !tmpYDataColumn) { DEBUG(" data columns not available"); return; } DEBUG(" x data rows = " << tmpXDataColumn->rowCount()); nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; int degree = fitData.degree; DEBUG(" fit model type = " << modelType << ", degree = " << degree); QVector& paramStartValues = fitData.paramStartValues; //QVector* xVector = static_cast* >(tmpXDataColumn->data()); //double xmean = gsl_stats_mean(xVector->constData(), 1, tmpXDataColumn->rowCount()); double xmin = tmpXDataColumn->minimum(); double xmax = tmpXDataColumn->maximum(); //double ymin = tmpYDataColumn->minimum(); double ymax = tmpYDataColumn->maximum(); double xrange = xmax - xmin; //double yrange = ymax-ymin; DEBUG(" x min/max = " << xmin << ' ' << xmax); //DEBUG(" y min/max = " << ymin << ' ' << ymax); // guess start values for parameter switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // not needed (works anyway) break; //TODO: handle basic models case nsl_fit_model_power: case nsl_fit_model_exponential: case nsl_fit_model_inverse_exponential: case nsl_fit_model_fourier: break; } break; case nsl_fit_model_peak: // use equidistant mu's and (xmax-xmin)/(10*degree) as sigma(, gamma) switch (modelType) { case nsl_fit_model_gaussian: case nsl_fit_model_lorentz: case nsl_fit_model_sech: case nsl_fit_model_logistic: for (int d = 0; d < degree; d++) { paramStartValues[3*d+2] = xmin + (d+1.)*xrange/(degree+1.); // mu paramStartValues[3*d+1] = xrange/(10.*degree); // sigma paramStartValues[3*d] = paramStartValues[3*d+1] * ymax; // A = sigma * ymax } break; case nsl_fit_model_voigt: for (int d = 0; d < degree; d++) { paramStartValues[4*d+1] = xmin + (d+1.)*xrange/(degree+1.); // mu paramStartValues[4*d+2] = xrange/(10.*degree); // sigma paramStartValues[4*d+3] = xrange/(10.*degree); // gamma } break; case nsl_fit_model_pseudovoigt1: for (int d = 0; d < degree; d++) { paramStartValues[4*d+1] = 0.5; // eta paramStartValues[4*d+2] = xrange/(10.*degree); // sigma paramStartValues[4*d+3] = xmin + (d+1.)*xrange/(degree+1.); // mu } break; } break; case nsl_fit_model_growth: switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: case nsl_fit_model_sigmoid: // use (xmax+xmin)/2 as mu and (xmax-xmin)/10 as sigma paramStartValues[1] = (xmax+xmin)/2.; paramStartValues[2] = xrange/10.; break; case nsl_fit_model_hill: paramStartValues[2] = xrange/10.; break; case nsl_fit_model_gompertz: //TODO break; } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: // mu paramStartValues[2] = (xmin+xmax)/2.; // sigma paramStartValues[1] = xrange/10.; // A = sigma * y_max paramStartValues[0] = paramStartValues[1] * ymax; break; //TODO: other types default: break; } break; case nsl_fit_model_custom: // not possible break; } } /*! * sets the parameter names for given model category, model type and degree in \c fitData for given action */ void XYFitCurve::initFitData(PlotDataDialog::AnalysisAction action) { if (!action) return; Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; if (action == PlotDataDialog::FitLinear) { //Linear fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_polynomial; fitData.degree = 1; } else if (action == PlotDataDialog::FitPower) { //Power fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_power; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp1) { //Exponential (degree 1) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_exponential; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp2) { //Exponential (degree 2) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_exponential; fitData.degree = 2; } else if (action == PlotDataDialog::FitInvExp) { //Inverse exponential fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_inverse_exponential; } else if (action == PlotDataDialog::FitGauss) { //Gauss fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = (int)nsl_fit_model_gaussian; fitData.degree = 1; } else if (action == PlotDataDialog::FitCauchyLorentz) { //Cauchy-Lorentz fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = (int)nsl_fit_model_lorentz; fitData.degree = 1; } else if (action == PlotDataDialog::FitTan) { //Arc tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_atan; } else if (action == PlotDataDialog::FitTanh) { //Hyperbolic tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_tanh; } else if (action == PlotDataDialog::FitErrFunc) { //Error function fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_erf; } else { //Custom fitData.modelCategory = nsl_fit_model_custom; fitData.modelType = 0; } XYFitCurve::initFitData(fitData); } /*! * sets the model expression and the parameter names for given model category, model type and degree in \c fitData */ void XYFitCurve::initFitData(XYFitCurve::FitData& fitData) { nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; QString& model = fitData.model; QStringList& paramNames = fitData.paramNames; QStringList& paramNamesUtf8 = fitData.paramNamesUtf8; int degree = fitData.degree; QVector& paramStartValues = fitData.paramStartValues; QVector& paramLowerLimits = fitData.paramLowerLimits; QVector& paramUpperLimits = fitData.paramUpperLimits; QVector& paramFixed = fitData.paramFixed; if (modelCategory != nsl_fit_model_custom) { DEBUG("XYFitCurve::initFitData() for model category = " << nsl_fit_model_category_name[modelCategory] << ", model type = " << modelType << ", degree = " << degree); paramNames.clear(); } else { DEBUG("XYFitCurve::initFitData() for model category = nsl_fit_model_custom, model type = " << modelType << ", degree = " << degree); } paramNamesUtf8.clear(); // 10 indices used in multi degree models QStringList indices = { UTF8_QSTRING("₁"), UTF8_QSTRING("₂"), UTF8_QSTRING("₃"), UTF8_QSTRING("₄"), UTF8_QSTRING("₅"), UTF8_QSTRING("₆"), UTF8_QSTRING("₇"), UTF8_QSTRING("₈"), UTF8_QSTRING("₉"), UTF8_QSTRING("₁₀")}; switch (modelCategory) { case nsl_fit_model_basic: model = nsl_fit_model_basic_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_polynomial: paramNames << "c0" << "c1"; paramNamesUtf8 << UTF8_QSTRING("c₀") << UTF8_QSTRING("c₁"); if (degree == 2) { model += " + c2*x^2"; paramNames << "c2"; paramNamesUtf8 << UTF8_QSTRING("c₂"); } else if (degree > 2) { for (int i = 2; i <= degree; ++i) { QString numStr = QString::number(i); model += "+c" + numStr + "*x^" + numStr; paramNames << 'c' + numStr; paramNamesUtf8 << 'c' + indices[i-1]; } } break; case nsl_fit_model_power: if (degree == 1) { paramNames << "a" << "b"; } else { paramNames << "a" << "b" << "c"; model = "a + b*x^c"; } break; case nsl_fit_model_exponential: if (degree == 1) { paramNames << "a" << "b"; } else { for (int i = 1; i <= degree; i++) { QString numStr = QString::number(i); if (i == 1) model = "a1*exp(b1*x)"; else model += " + a" + numStr + "*exp(b" + numStr + "*x)"; paramNames << 'a' + numStr << 'b' + numStr; paramNamesUtf8 << 'a' + indices[i-1] << 'b' + indices[i-1]; } } break; case nsl_fit_model_inverse_exponential: paramNames << "a" << "b" << "c"; break; case nsl_fit_model_fourier: paramNames << "w" << "a0" << "a1" << "b1"; paramNamesUtf8 << UTF8_QSTRING("ω") << UTF8_QSTRING("a₀") << UTF8_QSTRING("a₁") << UTF8_QSTRING("b₁"); if (degree > 1) { for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); model += "+ (a" + numStr + "*cos(" + numStr + "*w*x) + b" + numStr + "*sin(" + numStr + "*w*x))"; paramNames << 'a' + numStr << 'b' + numStr; paramNamesUtf8 << 'a' + indices[i-1] << 'b' + indices[i-1]; } } break; } break; case nsl_fit_model_peak: model = nsl_fit_model_peak_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_gaussian: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1./sqrt(2*pi) * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* exp(-((x-mu" + numStr + ")/s" + numStr + ")^2/2)"; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_lorentz: switch (degree) { case 1: paramNames << "a" << "g" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("μ"); break; default: model = "1./pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + " * g" + numStr + "/(g" + numStr + "^2+(x-mu" + numStr + ")^2)"; paramNames << 'a' + numStr << 'g' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("γ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_sech: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1/pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/s" + numStr + ')'; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_logistic: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1/4 * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/2/s" + numStr + ")**2"; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_voigt: switch (degree) { case 1: paramNames << "a" << "mu" << "s" << "g"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << UTF8_QSTRING("σ") << UTF8_QSTRING("γ"); break; default: model.clear(); for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "*voigt(x-mu" + numStr + ",s" + numStr + ",g" + numStr + ')'; paramNames << 'a' + numStr << "mu" + numStr << 's' + numStr << 'g' + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("γ") + indices[i-1]; } } break; case nsl_fit_model_pseudovoigt1: switch (degree) { case 1: paramNames << "a" << "et" << "w" << "mu"; // eta function exists! paramNamesUtf8 << "A" << UTF8_QSTRING("η") << "w" << UTF8_QSTRING("μ"); break; default: model.clear(); for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "*pseudovoigt1(x-mu" + numStr + ",eta" + numStr + ",w" + numStr + ')'; paramNames << 'a' + numStr << "eta" + numStr << 'w' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("η") + indices[i-1] << 'w' + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } } break; } break; case nsl_fit_model_growth: model = nsl_fit_model_growth_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: paramNames << "a" << "mu" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << UTF8_QSTRING("σ"); break; case nsl_fit_model_sigmoid: paramNames << "a" << "mu" << "k"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << "k"; break; case nsl_fit_model_hill: paramNames << "a" << "n" << "a"; paramNamesUtf8 << "A" << "n" << UTF8_QSTRING("σ"); break; case nsl_fit_model_gompertz: paramNames << "a" << "b" << "c"; break; } break; case nsl_fit_model_distribution: model = nsl_sf_stats_distribution_equation[fitData.modelType]; switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_gaussian_tail: paramNames << "A" << "s" << "a" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << "a" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_exponential: paramNames << "a" << "l" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("λ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_exponential_power: paramNames << "a" << "s" << "b" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << "b" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: paramNames << "a" << "g" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_rayleigh: paramNames << "a" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ"); break; case nsl_sf_stats_landau: paramNames << "a"; paramNamesUtf8 << "A"; break; case nsl_sf_stats_levy_alpha_stable: // unused distributions case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; case nsl_sf_stats_gamma: paramNames << "a" << "k" << "t"; paramNamesUtf8 << "A"<< "k" << UTF8_QSTRING("θ"); break; case nsl_sf_stats_flat: paramNames << "A" << "b" << "a"; break; case nsl_sf_stats_chi_squared: paramNames << "a" << "n"; paramNamesUtf8 << "A" << "n"; break; case nsl_sf_stats_fdist: paramNames << "a" << "n1" << "n2"; paramNamesUtf8 << "A" << UTF8_QSTRING("ν₁") << UTF8_QSTRING("ν₂"); break; case nsl_sf_stats_tdist: paramNames << "a" << "n"; paramNamesUtf8 << "A" << UTF8_QSTRING("ν"); break; case nsl_sf_stats_beta: case nsl_sf_stats_pareto: paramNames << "A" << "a" << "b"; break; case nsl_sf_stats_weibull: paramNames << "a" << "k" << "l" << "mu"; paramNamesUtf8 << "A" << "k" << UTF8_QSTRING("λ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_gumbel1: paramNames << "a" << "s" << "mu" << "b"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << UTF8_QSTRING("β"); break; case nsl_sf_stats_gumbel2: paramNames << "A" << "a" << "b" << "mu"; paramNamesUtf8 << "A" << "a" << "b" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_poisson: paramNames << "a" << "l"; paramNamesUtf8 << "A" << UTF8_QSTRING("λ"); break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: paramNames << "a" << "p" << "n"; paramNamesUtf8 << "A" << "p" << "n"; break; case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: paramNames << "a" << "p"; paramNamesUtf8 << "A" << "p"; break; case nsl_sf_stats_hypergeometric: paramNames << "a" << "n1" << "n2" << "t"; paramNamesUtf8 << "A" << UTF8_QSTRING("n₁") << UTF8_QSTRING("n₂") << "t"; break; case nsl_sf_stats_maxwell_boltzmann: paramNames << "a" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ"); break; case nsl_sf_stats_frechet: paramNames << "a" << "g" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; } break; case nsl_fit_model_custom: break; } DEBUG("model: " << STDSTRING(model)); if (paramNamesUtf8.isEmpty()) paramNamesUtf8 << paramNames; //resize the vector for the start values and set the elements to 1.0 //in case a custom model is used, do nothing, we take over the previous values if (modelCategory != nsl_fit_model_custom) { const int np = paramNames.size(); paramStartValues.resize(np); paramFixed.resize(np); paramLowerLimits.resize(np); paramUpperLimits.resize(np); for (int i = 0; i < np; ++i) { paramStartValues[i] = 1.0; paramFixed[i] = false; paramLowerLimits[i] = -std::numeric_limits::max(); paramUpperLimits[i] = std::numeric_limits::max(); } // set some model-dependent start values // TODO: see initStartValues() if (modelCategory == nsl_fit_model_distribution) { if (modelType == (int)nsl_sf_stats_flat) paramStartValues[2] = -1.0; else if (modelType == (int)nsl_sf_stats_levy) paramStartValues[2] = 0.0; else if (modelType == (int)nsl_sf_stats_exponential_power || modelType == (int)nsl_sf_stats_weibull || modelType == (int)nsl_sf_stats_gumbel2 || modelType == (int)nsl_sf_stats_frechet) paramStartValues[3] = 0.0; else if (modelType == (int)nsl_sf_stats_binomial || modelType == (int)nsl_sf_stats_negative_binomial || modelType == (int)nsl_sf_stats_pascal || modelType == (int)nsl_sf_stats_geometric || modelType == (int)nsl_sf_stats_logarithmic) paramStartValues[1] = 0.5; } } } /*! Returns an icon to be used in the project explorer. */ QIcon XYFitCurve::icon() const { return QIcon::fromTheme("labplot-xy-fit-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xErrorColumn, xErrorColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yErrorColumn, yErrorColumn) const QString& XYFitCurve::xErrorColumnPath() const { Q_D(const XYFitCurve); return d->xErrorColumnPath; } const QString& XYFitCurve::yErrorColumnPath() const { Q_D(const XYFitCurve); return d->yErrorColumnPath; } BASIC_SHARED_D_READER_IMPL(XYFitCurve, XYFitCurve::FitData, fitData, fitData) const XYFitCurve::FitResult& XYFitCurve::fitResult() const { Q_D(const XYFitCurve); return d->fitResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYFitCurve, SetXErrorColumn, const AbstractColumn*, xErrorColumn) void XYFitCurve::setXErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xErrorColumn) { exec(new XYFitCurveSetXErrorColumnCmd(d, column, ki18n("%1: assign x-error"))); handleSourceDataChanged(); if (column) { connect(column, &AbstractColumn::dataChanged, this, [=](){ handleSourceDataChanged(); }); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetYErrorColumn, const AbstractColumn*, yErrorColumn) void XYFitCurve::setYErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yErrorColumn) { exec(new XYFitCurveSetYErrorColumnCmd(d, column, ki18n("%1: assign y-error"))); handleSourceDataChanged(); if (column) { connect(column, &AbstractColumn::dataChanged, this, [=](){ handleSourceDataChanged(); }); //TODO disconnect on undo } } } // do not recalculate (allow preview) //STD_SETTER_CMD_IMPL_F_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData, recalculate) STD_SETTER_CMD_IMPL_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData) void XYFitCurve::setFitData(const XYFitCurve::FitData& fitData) { Q_D(XYFitCurve); exec(new XYFitCurveSetFitDataCmd(d, fitData, ki18n("%1: set fit options and perform the fit"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFitCurvePrivate::XYFitCurvePrivate(XYFitCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) {} //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYFitCurvePrivate::~XYFitCurvePrivate() = default; // data structure to pass parameter to fit functions struct data { size_t n; //number of data points double* x; //pointer to the vector with x-data values double* y; //pointer to the vector with y-data values double* weight; //pointer to the vector with weight values nsl_fit_model_category modelCategory; int modelType; int degree; QString* func; // string containing the definition of the model/function QStringList* paramNames; double* paramMin; // lower parameter limits double* paramMax; // upper parameter limits bool* paramFixed; // parameter fixed? }; /*! * \param paramValues vector containing current values of the fit parameters * \param params * \param f vector with the weighted residuals weight[i]*(Yi - y[i]) */ int func_f(const gsl_vector* paramValues, void* params, gsl_vector* f) { //DEBUG("func_f"); size_t n = ((struct data*)params)->n; double* x = ((struct data*)params)->x; double* y = ((struct data*)params)->y; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; QByteArray funcba = ((struct data*)params)->func->toLatin1(); // a local byte array is needed! const char *func = funcba.constData(); // function to evaluate QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; // set current values of the parameters for (int i = 0; i < paramNames->size(); i++) { double v = gsl_vector_get(paramValues, (size_t)i); // bound values if limits are set QByteArray paramnameba = paramNames->at(i).toLatin1(); assign_variable(paramnameba.constData(), nsl_fit_map_bound(v, min[i], max[i])); QDEBUG("Parameter"<n; double* xVector = ((struct data*)params)->x; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; unsigned int degree = ((struct data*)params)->degree; QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; bool *fixed = ((struct data*)params)->paramFixed; // calculate the Jacobian matrix: // Jacobian matrix J(i,j) = df_i / dx_j // where f_i = w_i*(Y_i - y_i), // Y_i = model and the x_j are the parameters double x; switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // Y(x) = c0 + c1*x + ... + cn*x^n for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < (unsigned int)paramNames->size(); ++j) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_polynomial_param_deriv(x, j, weight[i])); } } break; case nsl_fit_model_power: // Y(x) = a*x^b or Y(x) = a + b*x^c. if (degree == 1) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power1_param_deriv(j, x, a, b, weight[i])); } } } else if (degree == 2) { const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double c = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power2_param_deriv(j, x, b, c, weight[i])); } } } break; case nsl_fit_model_exponential: { // Y(x) = a*exp(b*x) + c*exp(d*x) + ... double *p = new double[2*degree]; for (unsigned int i = 0; i < 2*degree; i++) p[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, i), min[i], max[i]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2*degree; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponentialn_param_deriv(j, x, p, weight[i])); } } delete[] p; break; } case nsl_fit_model_inverse_exponential: { // Y(x) = a*(1-exp(b*x))+c const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_inverse_exponential_param_deriv(j, x, a, b, weight[i])); } } break; } case nsl_fit_model_fourier: { // Y(x) = a0 + (a1*cos(w*x) + b1*sin(w*x)) + ... + (an*cos(n*w*x) + bn*sin(n*w*x) //parameters: w, a0, a1, b1, ... an, bn double* a = new double[degree]; double* b = new double[degree]; double w = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); a[0] = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); b[0] = 0; for (unsigned int i = 1; i < degree; ++i) { a[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i), min[2*i], max[2*i]); b[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i+1), min[2*i+1], max[2*i+1]); } for (size_t i = 0; i < n; i++) { x = xVector[i]; double wd = 0; //first derivative with respect to the w parameter for (unsigned int j = 1; j < degree; ++j) { wd += -a[j]*j*x*sin(j*w*x) + b[j]*j*x*cos(j*w*x); } gsl_matrix_set(J, i, 0, weight[i]*wd); gsl_matrix_set(J, i, 1, weight[i]); for (unsigned int j = 1; j <= degree; ++j) { gsl_matrix_set(J, (size_t)i, (size_t)(2*j), nsl_fit_model_fourier_param_deriv(0, j, x, w, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(2*j+1), nsl_fit_model_fourier_param_deriv(1, j, x, w, weight[i])); } for (unsigned int j = 0; j <= 2*degree+1; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } delete[] a; delete[] b; break; } } break; case nsl_fit_model_peak: switch (modelType) { case nsl_fit_model_gaussian: case nsl_fit_model_lorentz: case nsl_fit_model_sech: case nsl_fit_model_logistic: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j), min[3*j], max[3*j]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+1), min[3*j+1], max[3*j+1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+2), min[3*j+2], max[3*j+2]); switch (modelType) { case nsl_fit_model_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_gaussian_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_gaussian_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_gaussian_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_lorentz: // a,s,t gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_lorentz_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_lorentz_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_lorentz_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_sech: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_sech_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_sech_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_sech_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_logistic: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_logistic_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_logistic_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_logistic_param_deriv(2, x, a, s, mu, weight[i])); break; } } for (unsigned int j = 0; j < 3*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; case nsl_fit_model_voigt: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j), min[4*j], max[4*j]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+1), min[4*j+1], max[4*j+1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+2), min[4*j+2], max[4*j+2]); const double g = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+3), min[4*j+3], max[4*j+3]); gsl_matrix_set(J, (size_t)i, (size_t)(4*j), nsl_fit_model_voigt_param_deriv(0, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+1), nsl_fit_model_voigt_param_deriv(1, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+2), nsl_fit_model_voigt_param_deriv(2, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+3), nsl_fit_model_voigt_param_deriv(3, x, a, mu, s, g, weight[i])); } for (unsigned int j = 0; j < 4*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; case nsl_fit_model_pseudovoigt1: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j), min[4*j], max[4*j]); const double eta = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+1), min[4*j+1], max[4*j+1]); const double w = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+2), min[4*j+2], max[4*j+2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+3), min[4*j+3], max[4*j+3]); gsl_matrix_set(J, (size_t)i, (size_t)(4*j), nsl_fit_model_voigt_param_deriv(0, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+1), nsl_fit_model_voigt_param_deriv(1, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+2), nsl_fit_model_voigt_param_deriv(2, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+3), nsl_fit_model_voigt_param_deriv(3, x, a, eta, w, mu, weight[i])); } for (unsigned int j = 0; j < 4*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; } break; case nsl_fit_model_growth: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) { gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } else { switch (modelType) { case nsl_fit_model_atan: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_atan_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_tanh: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_tanh_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_algebraic_sigmoid: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_algebraic_sigmoid_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_sigmoid: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sigmoid_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_erf: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_erf_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_hill: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hill_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_gompertz: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gompertz_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_gudermann: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gudermann_param_deriv(j, x, a, mu, s, weight[i])); break; } } } } } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_exponential: case nsl_sf_stats_laplace: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_levy: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_exponential: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponential_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_laplace: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_laplace_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_cauchy_lorentz: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lorentz_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_rayleigh_tail: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_tail_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_lognormal: if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lognormal_param_deriv(j, x, a, s, mu, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); break; case nsl_sf_stats_logistic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logistic_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_sech: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sech_dist_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_levy: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_levy_param_deriv(j, x, a, s, mu, weight[i])); break; } } } } break; } case nsl_sf_stats_gaussian_tail: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_tail_param_deriv(j, x, A, s, a, mu, weight[i])); } } break; } case nsl_sf_stats_exponential_power: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exp_pow_param_deriv(j, x, a, s, b, mu, weight[i])); } } break; } case nsl_sf_stats_rayleigh: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_param_deriv(j, x, a, s, weight[i])); } } break; } case nsl_sf_stats_gamma: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gamma_param_deriv(j, x, a, k, t, weight[i])); } } break; } case nsl_sf_stats_flat: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_flat_param_deriv(j, x, A, b, a, weight[i])); } } break; } case nsl_sf_stats_chi_squared: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_chi_square_param_deriv(j, x, a, nu, weight[i])); } } break; } case nsl_sf_stats_tdist: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_students_t_param_deriv(j, x, a, nu, weight[i])); } } break; } case nsl_sf_stats_fdist: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_fdist_param_deriv(j, x, a, n1, n2, weight[i])); } } break; } case nsl_sf_stats_beta: case nsl_sf_stats_pareto: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_beta: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_beta_param_deriv(j, x, A, a, b, weight[i])); break; case nsl_sf_stats_pareto: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pareto_param_deriv(j, x, A, a, b, weight[i])); break; } } } } break; } case nsl_sf_stats_weibull: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_weibull_param_deriv(j, x, a, k, l, mu, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } } } break; } case nsl_sf_stats_gumbel1: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel1_param_deriv(j, x, a, s, mu, b, weight[i])); } } break; } case nsl_sf_stats_gumbel2: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel2_param_deriv(j, x, A, a, b, mu, weight[i])); } } break; } case nsl_sf_stats_poisson: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_poisson_param_deriv(j, x, a, l, weight[i])); } } break; } case nsl_sf_stats_maxwell_boltzmann: { // Y(x) = a*sqrt(2/pi) * x^2/s^3 * exp(-(x/s)^2/2) const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_maxwell_param_deriv(j, x, a, s, weight[i])); } } break; } case nsl_sf_stats_frechet: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double g = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_frechet_param_deriv(j, x, a, g, s, mu, weight[i])); } } break; } case nsl_sf_stats_landau: { // const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); for (size_t i = 0; i < n; i++) { x = xVector[i]; if (fixed[0]) gsl_matrix_set(J, (size_t)i, 0, 0.); else gsl_matrix_set(J, (size_t)i, 0, nsl_fit_model_landau_param_deriv(0, x, weight[i])); } break; } case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double N = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_binomial_param_deriv(j, x, a, p, N, weight[i])); break; case nsl_sf_stats_negative_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_negative_binomial_param_deriv(j, x, a, p, N, weight[i])); break; case nsl_sf_stats_pascal: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pascal_param_deriv(j, x, a, p, N, weight[i])); break; } } } } break; } case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_geometric: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_geometric_param_deriv(j, x, a, p, weight[i])); break; case nsl_sf_stats_logarithmic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logarithmic_param_deriv(j, x, a, p, weight[i])); break; } } } } break; } case nsl_sf_stats_hypergeometric: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hypergeometric_param_deriv(j, x, a, n1, n2, t, weight[i])); } } break; } // unused distributions case nsl_sf_stats_levy_alpha_stable: case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; } break; case nsl_fit_model_custom: QByteArray funcba = ((struct data*)params)->func->toLatin1(); const char* func = funcba.data(); QByteArray nameba; double value; const unsigned int np = paramNames->size(); for (size_t i = 0; i < n; i++) { x = xVector[i]; assign_variable("x", x); for (unsigned int j = 0; j < np; j++) { for (unsigned int k = 0; k < np; k++) { if (k != j) { nameba = paramNames->at(k).toLatin1(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, k), min[k], max[k]); assign_variable(nameba.data(), value); } } nameba = paramNames->at(j).toLatin1(); const char *name = nameba.data(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, j), min[j], max[j]); assign_variable(name, value); const double f_p = parse(func); double eps = 1.e-9; if (std::abs(f_p) > 0) eps *= std::abs(f_p); // scale step size with function value value += eps; assign_variable(name, value); const double f_pdp = parse(func); // DEBUG("evaluate deriv"<* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); residualsVector = static_cast* >(residualsColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->addChild(residualsColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { DEBUG(" Clear columns") xVector->clear(); yVector->clear(); //TODO: residualsVector->clear(); } DEBUG("XYFitCurvePrivate::prepareResultColumns() DONE") } void XYFitCurvePrivate::recalculate() { DEBUG("XYFitCurvePrivate::recalculate()"); QElapsedTimer timer; timer.start(); // prepare source data columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { DEBUG(" spreadsheet columns as data source"); tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { DEBUG(" curve columns as data source"); tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } // clear the previous result fitResult = XYFitCurve::FitResult(); if (!tmpXDataColumn || !tmpYDataColumn) { DEBUG(" ERROR: Preparing source data columns failed!"); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } prepareResultColumns(); //fit settings const unsigned int maxIters = fitData.maxIterations; //maximal number of iterations const double delta = fitData.eps; //fit tolerance const unsigned int np = fitData.paramNames.size(); //number of fit parameters if (np == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Model has no parameters."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (yErrorColumn) { if (yErrorColumn->rowCount() < tmpXDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Not sufficient weight data points provided."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } } //copy all valid data point for the fit to temporary vectors QVector xdataVector; QVector ydataVector; QVector xerrorVector; QVector yerrorVector; double xmin, xmax; if (fitData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = fitData.fitRange.first(); xmax = fitData.fitRange.last(); } DEBUG(" fit range = " << xmin << " .. " << xmax); //logic from XYAnalysisCurve::copyData(), extended by the handling of error columns. //TODO: decide how to deal with non-numerical error columns int rowCount = qMin(tmpXDataColumn->rowCount(), tmpYDataColumn->rowCount()); for (int row = 0; row < rowCount; ++row) { // omit invalid data if (!tmpXDataColumn->isValid(row) || tmpXDataColumn->isMasked(row) || !tmpYDataColumn->isValid(row) || tmpYDataColumn->isMasked(row)) continue; double x = NAN; switch (tmpXDataColumn->columnMode()) { case AbstractColumn::ColumnMode::Numeric: x = tmpXDataColumn->valueAt(row); break; case AbstractColumn::ColumnMode::Integer: x = tmpXDataColumn->integerAt(row); break; case AbstractColumn::ColumnMode::BigInt: x = tmpXDataColumn->bigIntAt(row); break; case AbstractColumn::ColumnMode::Text: // not valid break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Day: case AbstractColumn::ColumnMode::Month: x = tmpXDataColumn->dateTimeAt(row).toMSecsSinceEpoch(); } double y = NAN; switch (tmpYDataColumn->columnMode()) { case AbstractColumn::ColumnMode::Numeric: y = tmpYDataColumn->valueAt(row); break; case AbstractColumn::ColumnMode::Integer: y = tmpYDataColumn->integerAt(row); break; case AbstractColumn::ColumnMode::BigInt: y = tmpYDataColumn->bigIntAt(row); break; case AbstractColumn::ColumnMode::Text: // not valid break; case AbstractColumn::ColumnMode::DateTime: case AbstractColumn::ColumnMode::Day: case AbstractColumn::ColumnMode::Month: y = tmpYDataColumn->dateTimeAt(row).toMSecsSinceEpoch(); } // only when inside given range if (x >= xmin && x <= xmax) { if ((!xErrorColumn && !yErrorColumn) || !fitData.useDataErrors) { // x-y xdataVector.append(x); ydataVector.append(y); } else if (!xErrorColumn && yErrorColumn) { // x-y-dy if (!std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(x); ydataVector.append(y); yerrorVector.append(yErrorColumn->valueAt(row)); } } else if (xErrorColumn && yErrorColumn) { // x-y-dx-dy if (!std::isnan(xErrorColumn->valueAt(row)) && !std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(x); ydataVector.append(y); xerrorVector.append(xErrorColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } } } //number of data points to fit const size_t n = xdataVector.size(); DEBUG(" number of data points: " << n); if (n == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("No data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (n < np) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("The number of data points (%1) must be greater than or equal to the number of parameters (%2).", n, np); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (fitData.model.simplified().isEmpty()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Fit model not specified."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* xerror = xerrorVector.data(); // size may be 0 double* yerror = yerrorVector.data(); // size may be 0 DEBUG(" x error vector size: " << xerrorVector.size()); DEBUG(" y error vector size: " << yerrorVector.size()); double* weight = new double[n]; for (size_t i = 0; i < n; i++) weight[i] = 1.; const double minError = 1.e-199; // minimum error for weighting switch (fitData.yWeightsType) { case nsl_fit_weight_no: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative_fit: break; case nsl_fit_weight_instrumental: // yerror are sigmas for (int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./gsl_pow_2(qMax(yerror[i], qMax(sqrt(minError), fabs(ydata[i]) * 1.e-15))); break; case nsl_fit_weight_direct: // yerror are weights for (int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = yerror[i]; break; case nsl_fit_weight_inverse: // yerror are inverse weights for (int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./qMax(yerror[i], qMax(minError, fabs(ydata[i]) * 1.e-15)); break; case nsl_fit_weight_statistical: for (int i = 0; i < (int)n; i++) weight[i] = 1./qMax(ydata[i], minError); break; case nsl_fit_weight_relative: for (int i = 0; i < (int)n; i++) weight[i] = 1./qMax(gsl_pow_2(ydata[i]), minError); break; } /////////////////////// GSL >= 2 has a complete new interface! But the old one is still supported. /////////////////////////// // GSL >= 2 : "the 'fdf' field of gsl_multifit_function_fdf is now deprecated and does not need to be specified for nonlinear least squares problems" unsigned int nf = 0; // number of fixed parameter for (unsigned int i = 0; i < np; i++) { const bool fixed = fitData.paramFixed.data()[i]; if (fixed) nf++; DEBUG(" parameter " << i << " fixed: " << fixed); } //function to fit gsl_multifit_function_fdf f; DEBUG(" model = " << STDSTRING(fitData.model)); struct data params = {n, xdata, ydata, weight, fitData.modelCategory, fitData.modelType, fitData.degree, &fitData.model, &fitData.paramNames, fitData.paramLowerLimits.data(), fitData.paramUpperLimits.data(), fitData.paramFixed.data()}; f.f = &func_f; f.df = &func_df; f.fdf = &func_fdf; f.n = n; f.p = np; f.params = ¶ms; DEBUG(" initialize the derivative solver (using Levenberg-Marquardt robust solver)"); const gsl_multifit_fdfsolver_type* T = gsl_multifit_fdfsolver_lmsder; gsl_multifit_fdfsolver* s = gsl_multifit_fdfsolver_alloc(T, n, np); DEBUG(" set start values"); double* x_init = fitData.paramStartValues.data(); double* x_min = fitData.paramLowerLimits.data(); double* x_max = fitData.paramUpperLimits.data(); DEBUG(" scale start values if limits are set"); for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_unbound(x_init[i], x_min[i], x_max[i]); DEBUG(" DONE"); gsl_vector_view x = gsl_vector_view_array(x_init, np); DEBUG(" Turning off GSL error handler to avoid overflow/underflow"); gsl_set_error_handler_off(); DEBUG(" Initialize solver with function f and initial guess x"); gsl_multifit_fdfsolver_set(s, &f, &x.vector); DEBUG(" Iterate ..."); int status; unsigned int iter = 0; fitResult.solverOutput.clear(); writeSolverState(s); do { iter++; DEBUG(" iter " << iter); // update weights for Y-depending weights (using function values from residuals) if (fitData.yWeightsType == nsl_fit_weight_statistical_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i)/sqrt(weight[i]) + ydata[i]); // 1/Y_i } else if (fitData.yWeightsType == nsl_fit_weight_relative_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(gsl_vector_get(s->f, i)/sqrt(weight[i]) + ydata[i]); // 1/Y_i^2 } DEBUG(" run fdfsolver_iterate"); status = gsl_multifit_fdfsolver_iterate(s); DEBUG(" fdfsolver_iterate DONE"); writeSolverState(s); if (status) { DEBUG(" iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); DEBUG(" iter " << iter << ", test status = " << status); } while (status == GSL_CONTINUE && iter < maxIters); // second run for x-error fitting if (xerrorVector.size() > 0) { DEBUG(" Rerun fit with x errors"); unsigned int iter2 = 0; double chisq = 0, chisqOld = 0; double *fun = new double[n]; do { iter2++; chisqOld = chisq; //printf("iter2 = %d\n", iter2); // calculate function from residuals for (size_t i = 0; i < n; i++) fun[i] = gsl_vector_get(s->f, i) * 1./sqrt(weight[i]) + ydata[i]; // calculate weight[i] for (size_t i = 0; i < n; i++) { // calculate df[i] size_t index = i-1; if (i == 0) index = i; if (i == n-1) index = i-2; double df = (fun[index+1] - fun[index])/(xdata[index+1] - xdata[index]); //printf("df = %g\n", df); double sigmasq = 1.; switch (fitData.xWeightsType) { // x-error type: f'(x)^2*s_x^2 = f'(x)/w_x case nsl_fit_weight_no: break; case nsl_fit_weight_direct: // xerror = w_x sigmasq = df*df/qMax(xerror[i], minError); break; case nsl_fit_weight_instrumental: // xerror = s_x sigmasq = df*df*xerror[i]*xerror[i]; break; case nsl_fit_weight_inverse: // xerror = 1/w_x = s_x^2 sigmasq = df*df*xerror[i]; break; case nsl_fit_weight_statistical: // s_x^2 = 1/w_x = x sigmasq = xdata[i]; break; case nsl_fit_weight_relative: // s_x^2 = 1/w_x = x^2 sigmasq = xdata[i]*xdata[i]; break; case nsl_fit_weight_statistical_fit: // unused case nsl_fit_weight_relative_fit: break; } if (yerrorVector.size() > 0) { switch (fitData.yWeightsType) { // y-error types: s_y^2 = 1/w_y case nsl_fit_weight_no: break; case nsl_fit_weight_direct: // yerror = w_y sigmasq += 1./qMax(yerror[i], minError); break; case nsl_fit_weight_instrumental: // yerror = s_y sigmasq += yerror[i]*yerror[i]; break; case nsl_fit_weight_inverse: // yerror = 1/w_y sigmasq += yerror[i]; break; case nsl_fit_weight_statistical: // unused case nsl_fit_weight_relative: break; case nsl_fit_weight_statistical_fit: // s_y^2 = 1/w_y = Y_i sigmasq += fun[i]; break; case nsl_fit_weight_relative_fit: // s_y^2 = 1/w_y = Y_i^2 sigmasq += fun[i]*fun[i]; break; } } //printf ("sigma[%d] = %g\n", i, sqrt(sigmasq)); weight[i] = 1./qMax(sigmasq, minError); } // update weights gsl_multifit_fdfsolver_set(s, &f, &x.vector); do { // fit iter++; writeSolverState(s); status = gsl_multifit_fdfsolver_iterate(s); //printf ("status = %s\n", gsl_strerror (status)); if (status) { DEBUG(" iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); chisq = gsl_blas_dnrm2(s->f); } while (iter2 < maxIters && fabs(chisq-chisqOld) > fitData.eps); delete[] fun; } delete[] weight; // unscale start parameter for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_bound(x_init[i], x_min[i], x_max[i]); //get the covariance matrix //TODO: scale the Jacobian when limits are used before constructing the covar matrix? gsl_matrix* covar = gsl_matrix_alloc(np, np); #if GSL_MAJOR_VERSION >= 2 // the Jacobian is not part of the solver anymore gsl_matrix *J = gsl_matrix_alloc(s->fdf->n, s->fdf->p); gsl_multifit_fdfsolver_jac(s, J); gsl_multifit_covar(J, 0.0, covar); #else gsl_multifit_covar(s->J, 0.0, covar); #endif //write the result fitResult.available = true; fitResult.valid = true; fitResult.status = gslErrorToString(status); fitResult.iterations = iter; fitResult.dof = n - (np - nf); // samples - (parameter - fixed parameter) //gsl_blas_dnrm2() - computes the Euclidian norm (||r||_2 = \sqrt {\sum r_i^2}) of the vector with the elements weight[i]*(Yi - y[i]) //gsl_blas_dasum() - computes the absolute sum \sum |r_i| of the elements of the vector with the elements weight[i]*(Yi - y[i]) fitResult.sse = gsl_pow_2(gsl_blas_dnrm2(s->f)); if (fitResult.dof != 0) { fitResult.rms = fitResult.sse/fitResult.dof; fitResult.rsd = sqrt(fitResult.rms); } fitResult.mse = fitResult.sse/n; fitResult.rmse = sqrt(fitResult.mse); fitResult.mae = gsl_blas_dasum(s->f)/n; // SST needed for coefficient of determination, R-squared fitResult.sst = gsl_stats_tss(ydata, 1, n); // for a linear model without intercept R-squared is calculated differently // see https://cran.r-project.org/doc/FAQ/R-FAQ.html#Why-does-summary_0028_0029-report-strange-results-for-the-R_005e2-estimate-when-I-fit-a-linear-model-with-no-intercept_003f if (fitData.modelCategory == nsl_fit_model_basic && fitData.modelType == nsl_fit_model_polynomial && fitData.degree == 1 && x_init[0] == 0) { DEBUG(" Using alternative R^2 for linear model without intercept"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } if (fitResult.sst < fitResult.sse) { DEBUG(" Using alternative R^2 since R^2 would be negative (probably custom model without intercept)"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } fitResult.rsquare = nsl_stats_rsquare(fitResult.sse, fitResult.sst); fitResult.rsquareAdj = nsl_stats_rsquareAdj(fitResult.rsquare, np, fitResult.dof, 1); fitResult.chisq_p = nsl_stats_chisq_p(fitResult.sse, fitResult.dof); fitResult.fdist_F = nsl_stats_fdist_F(fitResult.sst, fitResult.rms, np, 1); fitResult.fdist_p = nsl_stats_fdist_p(fitResult.fdist_F, np, fitResult.dof); fitResult.logLik = nsl_stats_logLik(fitResult.sse, n); fitResult.aic = nsl_stats_aic(fitResult.sse, n, np, 1); fitResult.bic = nsl_stats_bic(fitResult.sse, n, np, 1); //parameter values // GSL: const double c = GSL_MAX_DBL(1., sqrt(fitResult.rms)); // increase error for poor fit // NIST: const double c = sqrt(fitResult.rms); // increase error for poor fit, decrease for good fit const double c = sqrt(fitResult.rms); fitResult.paramValues.resize(np); fitResult.errorValues.resize(np); fitResult.tdist_tValues.resize(np); fitResult.tdist_pValues.resize(np); fitResult.tdist_marginValues.resize(np); for (unsigned int i = 0; i < np; i++) { // scale resulting values if they are bounded fitResult.paramValues[i] = nsl_fit_map_bound(gsl_vector_get(s->x, i), x_min[i], x_max[i]); // use results as start values if desired if (fitData.useResults) { fitData.paramStartValues.data()[i] = fitResult.paramValues[i]; DEBUG(" saving parameter " << i << ": " << fitResult.paramValues[i] << ' ' << fitData.paramStartValues.data()[i]); } fitResult.errorValues[i] = c*sqrt(gsl_matrix_get(covar, i, i)); fitResult.tdist_tValues[i] = nsl_stats_tdist_t(fitResult.paramValues.at(i), fitResult.errorValues.at(i)); fitResult.tdist_pValues[i] = nsl_stats_tdist_p(fitResult.tdist_tValues.at(i), fitResult.dof); fitResult.tdist_marginValues[i] = nsl_stats_tdist_margin(0.05, fitResult.dof, fitResult.errorValues.at(i)); } // fill residuals vector. To get residuals on the correct x values, fill the rest with zeros. residualsVector->resize(tmpXDataColumn->rowCount()); DEBUG(" Residual vector size: " << residualsVector->size()) if (fitData.autoRange) { // evaluate full range of residuals xVector->resize(tmpXDataColumn->rowCount()); auto mode = tmpXDataColumn->columnMode(); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) if (mode == AbstractColumn::ColumnMode::Numeric) (*xVector)[i] = tmpXDataColumn->valueAt(i); else if (mode == AbstractColumn::ColumnMode::Integer) (*xVector)[i] = tmpXDataColumn->integerAt(i); else if (mode == AbstractColumn::ColumnMode::BigInt) (*xVector)[i] = tmpXDataColumn->bigIntAt(i); else if (mode == AbstractColumn::ColumnMode::DateTime) (*xVector)[i] = tmpXDataColumn->dateTimeAt(i).toMSecsSinceEpoch(); ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = parser->evaluateCartesian(fitData.model, xVector, residualsVector, fitData.paramNames, fitResult.paramValues); if (rc) { for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*residualsVector)[i] = tmpYDataColumn->valueAt(i) - (*residualsVector)[i]; } else { DEBUG(" ERROR: Failed parsing residuals") residualsVector->clear(); } } else { // only selected range size_t j = 0; for (int i = 0; i < tmpXDataColumn->rowCount(); i++) { if (tmpXDataColumn->valueAt(i) >= xmin && tmpXDataColumn->valueAt(i) <= xmax) residualsVector->data()[i] = - gsl_vector_get(s->f, j++); else // outside range residualsVector->data()[i] = 0; } } residualsColumn->setChanged(); //free resources gsl_multifit_fdfsolver_free(s); gsl_matrix_free(covar); //calculate the fit function (vectors) evaluate(); fitResult.elapsedTime = timer.elapsed(); sourceDataChangedSinceLastRecalc = false; DEBUG("XYFitCurvePrivate::recalculate() DONE"); } /* evaluate fit function (preview == true: use start values, default: false) */ void XYFitCurvePrivate::evaluate(bool preview) { DEBUG("XYFitCurvePrivate::evaluate() preview = " << preview); // prepare source data columns const AbstractColumn* tmpXDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { DEBUG(" spreadsheet columns as data source"); tmpXDataColumn = xDataColumn; } else { DEBUG(" curve columns as data source"); if (dataSourceCurve) tmpXDataColumn = dataSourceCurve->xColumn(); } if (!tmpXDataColumn) { DEBUG(" ERROR: Preparing source data column failed!"); recalcLogicalPoints(); emit q->dataChanged(); return; } //only needed for preview (else we have all columns) // should not harm even if not in preview now that residuals are not cleared if (preview) prepareResultColumns(); if (!xVector || !yVector) { DEBUG(" xVector or yVector not defined!"); recalcLogicalPoints(); emit q->dataChanged(); return; } if (fitData.model.simplified().isEmpty()) { DEBUG(" no fit-model specified."); recalcLogicalPoints(); emit q->dataChanged(); return; } ExpressionParser* parser = ExpressionParser::getInstance(); double xmin, xmax; if (fitData.autoEvalRange) { // evaluate fit on full data range xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { // use given range for evaluation xmin = fitData.evalRange.first(); xmax = fitData.evalRange.last(); } DEBUG(" eval range = " << xmin << " .. " << xmax); xVector->resize((int)fitData.evaluatedPoints); yVector->resize((int)fitData.evaluatedPoints); DEBUG(" vector size = " << xVector->size()); QVector paramValues = fitResult.paramValues; if (preview) // results not available yet paramValues = fitData.paramStartValues; bool rc = parser->evaluateCartesian(fitData.model, QString::number(xmin), QString::number(xmax), (int)fitData.evaluatedPoints, xVector, yVector, fitData.paramNames, paramValues); if (!rc) { DEBUG(" ERROR: Parsing fit function failed") xVector->clear(); yVector->clear(); residualsVector->clear(); } recalcLogicalPoints(); emit q->dataChanged(); DEBUG("XYFitCurvePrivate::evaluate() DONE"); } /*! * writes out the current state of the solver \c s */ void XYFitCurvePrivate::writeSolverState(gsl_multifit_fdfsolver* s) { QString state; //current parameter values, semicolon separated double* min = fitData.paramLowerLimits.data(); double* max = fitData.paramUpperLimits.data(); for (int i = 0; i < fitData.paramNames.size(); ++i) { const double x = gsl_vector_get(s->x, i); // map parameter if bounded state += QString::number(nsl_fit_map_bound(x, min[i], max[i])) + '\t'; } //current value of the chi2-function state += QString::number(gsl_pow_2(gsl_blas_dnrm2(s->f))); state += ';'; DEBUG(" chi = " << gsl_pow_2(gsl_blas_dnrm2(s->f))); fitResult.solverOutput += state; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFitCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFitCurve); writer->writeStartElement("xyFitCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fit-curve specific information //fit data - only save model expression and parameter names for custom model, otherwise they are set in XYFitCurve::initFitData() writer->writeStartElement("fitData"); WRITE_COLUMN(d->xErrorColumn, xErrorColumn); WRITE_COLUMN(d->yErrorColumn, yErrorColumn); writer->writeAttribute("autoRange", QString::number(d->fitData.autoRange)); writer->writeAttribute("fitRangeMin", QString::number(d->fitData.fitRange.first(), 'g', 15)); writer->writeAttribute("fitRangeMax", QString::number(d->fitData.fitRange.last(), 'g', 15)); writer->writeAttribute("modelCategory", QString::number(d->fitData.modelCategory)); writer->writeAttribute("modelType", QString::number(d->fitData.modelType)); writer->writeAttribute("xWeightsType", QString::number(d->fitData.xWeightsType)); writer->writeAttribute("weightsType", QString::number(d->fitData.yWeightsType)); writer->writeAttribute("degree", QString::number(d->fitData.degree)); if (d->fitData.modelCategory == nsl_fit_model_custom) writer->writeAttribute("model", d->fitData.model); writer->writeAttribute("maxIterations", QString::number(d->fitData.maxIterations)); writer->writeAttribute("eps", QString::number(d->fitData.eps, 'g', 15)); writer->writeAttribute("evaluatedPoints", QString::number(d->fitData.evaluatedPoints)); writer->writeAttribute("autoEvalRange", QString::number(d->fitData.autoEvalRange)); writer->writeAttribute("useDataErrors", QString::number(d->fitData.useDataErrors)); writer->writeAttribute("useResults", QString::number(d->fitData.useResults)); writer->writeAttribute("previewEnabled", QString::number(d->fitData.previewEnabled)); if (d->fitData.modelCategory == nsl_fit_model_custom) { writer->writeStartElement("paramNames"); foreach (const QString &name, d->fitData.paramNames) writer->writeTextElement("name", name); writer->writeEndElement(); } writer->writeStartElement("paramStartValues"); foreach (const double &value, d->fitData.paramStartValues) writer->writeTextElement("startValue", QString::number(value, 'g', 15)); writer->writeEndElement(); // use 16 digits to handle -DBL_MAX writer->writeStartElement("paramLowerLimits"); foreach (const double &limit, d->fitData.paramLowerLimits) writer->writeTextElement("lowerLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); // use 16 digits to handle DBL_MAX writer->writeStartElement("paramUpperLimits"); foreach (const double &limit, d->fitData.paramUpperLimits) writer->writeTextElement("upperLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); writer->writeStartElement("paramFixed"); foreach (const double &fixed, d->fitData.paramFixed) writer->writeTextElement("fixed", QString::number(fixed)); writer->writeEndElement(); writer->writeEndElement(); //"fitData" //fit results (generated columns and goodness of the fit) writer->writeStartElement("fitResult"); writer->writeAttribute("available", QString::number(d->fitResult.available)); writer->writeAttribute("valid", QString::number(d->fitResult.valid)); writer->writeAttribute("status", d->fitResult.status); writer->writeAttribute("iterations", QString::number(d->fitResult.iterations)); writer->writeAttribute("time", QString::number(d->fitResult.elapsedTime)); writer->writeAttribute("dof", QString::number(d->fitResult.dof)); writer->writeAttribute("sse", QString::number(d->fitResult.sse, 'g', 15)); writer->writeAttribute("sst", QString::number(d->fitResult.sst, 'g', 15)); writer->writeAttribute("rms", QString::number(d->fitResult.rms, 'g', 15)); writer->writeAttribute("rsd", QString::number(d->fitResult.rsd, 'g', 15)); writer->writeAttribute("mse", QString::number(d->fitResult.mse, 'g', 15)); writer->writeAttribute("rmse", QString::number(d->fitResult.rmse, 'g', 15)); writer->writeAttribute("mae", QString::number(d->fitResult.mae, 'g', 15)); writer->writeAttribute("rsquare", QString::number(d->fitResult.rsquare, 'g', 15)); writer->writeAttribute("rsquareAdj", QString::number(d->fitResult.rsquareAdj, 'g', 15)); writer->writeAttribute("chisq_p", QString::number(d->fitResult.chisq_p, 'g', 15)); writer->writeAttribute("fdist_F", QString::number(d->fitResult.fdist_F, 'g', 15)); writer->writeAttribute("fdist_p", QString::number(d->fitResult.fdist_p, 'g', 15)); writer->writeAttribute("aic", QString::number(d->fitResult.aic, 'g', 15)); writer->writeAttribute("bic", QString::number(d->fitResult.bic, 'g', 15)); writer->writeAttribute("solverOutput", d->fitResult.solverOutput); writer->writeStartElement("paramValues"); foreach (const double &value, d->fitResult.paramValues) writer->writeTextElement("value", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("errorValues"); foreach (const double &value, d->fitResult.errorValues) writer->writeTextElement("error", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_tValues"); foreach (const double &value, d->fitResult.tdist_tValues) writer->writeTextElement("tdist_t", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_pValues"); foreach (const double &value, d->fitResult.tdist_pValues) writer->writeTextElement("tdist_p", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_marginValues"); foreach (const double &value, d->fitResult.tdist_marginValues) writer->writeTextElement("tdist_margin", QString::number(value, 'g', 15)); writer->writeEndElement(); //save calculated columns if available if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->save(writer); d->yColumn->save(writer); d->residualsColumn->save(writer); } writer->writeEndElement(); //"fitResult" writer->writeEndElement(); //"xyFitCurve" } //! Load from XML bool XYFitCurve::load(XmlStreamReader* reader, bool preview) { DEBUG("XYFitCurve::load()"); Q_D(XYFitCurve); KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFitCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "fitData") { attribs = reader->attributes(); READ_COLUMN(xErrorColumn); READ_COLUMN(yErrorColumn); READ_INT_VALUE("autoRange", fitData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", fitData.fitRange.first()); // old name READ_DOUBLE_VALUE("xRangeMax", fitData.fitRange.last()); // old name READ_DOUBLE_VALUE("fitRangeMin", fitData.fitRange.first()); READ_DOUBLE_VALUE("fitRangeMax", fitData.fitRange.last()); READ_INT_VALUE("modelCategory", fitData.modelCategory, nsl_fit_model_category); READ_INT_VALUE("modelType", fitData.modelType, unsigned int); READ_INT_VALUE("xWeightsType", fitData.xWeightsType, nsl_fit_weight_type); READ_INT_VALUE("weightsType", fitData.yWeightsType, nsl_fit_weight_type); READ_INT_VALUE("degree", fitData.degree, int); if (d->fitData.modelCategory == nsl_fit_model_custom) { READ_STRING_VALUE("model", fitData.model); DEBUG("read model = " << STDSTRING(d->fitData.model)); } READ_INT_VALUE("maxIterations", fitData.maxIterations, int); READ_DOUBLE_VALUE("eps", fitData.eps); READ_INT_VALUE("fittedPoints", fitData.evaluatedPoints, size_t); // old name READ_INT_VALUE("evaluatedPoints", fitData.evaluatedPoints, size_t); READ_INT_VALUE("evaluateFullRange", fitData.autoEvalRange, bool); // old name READ_INT_VALUE("autoEvalRange", fitData.autoEvalRange, bool); READ_INT_VALUE("useDataErrors", fitData.useDataErrors, bool); READ_INT_VALUE("useResults", fitData.useResults, bool); READ_INT_VALUE("previewEnabled", fitData.previewEnabled, bool); //set the model expression and the parameter names (can be derived from the saved values for category, type and degree) XYFitCurve::initFitData(d->fitData); // remove default names and start values d->fitData.paramStartValues.clear(); } else if (!preview && reader->name() == "name") { // needed for custom model d->fitData.paramNames << reader->readElementText(); } else if (!preview && reader->name() == "startValue") { d->fitData.paramStartValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fixed") { d->fitData.paramFixed << (bool)reader->readElementText().toInt(); } else if (!preview && reader->name() == "lowerLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // -DBL_MAX results in conversion error d->fitData.paramLowerLimits << x; else d->fitData.paramLowerLimits << -std::numeric_limits::max(); } else if (!preview && reader->name() == "upperLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // DBL_MAX results in conversion error d->fitData.paramUpperLimits << x; else d->fitData.paramUpperLimits << std::numeric_limits::max(); } else if (!preview && reader->name() == "value") { d->fitResult.paramValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "error") { d->fitResult.errorValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_t") { d->fitResult.tdist_tValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_p") { d->fitResult.tdist_pValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_margin") { d->fitResult.tdist_marginValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fitResult") { attribs = reader->attributes(); READ_INT_VALUE("available", fitResult.available, int); READ_INT_VALUE("valid", fitResult.valid, int); READ_STRING_VALUE("status", fitResult.status); READ_INT_VALUE("iterations", fitResult.iterations, int); READ_INT_VALUE("time", fitResult.elapsedTime, int); READ_DOUBLE_VALUE("dof", fitResult.dof); READ_DOUBLE_VALUE("sse", fitResult.sse); READ_DOUBLE_VALUE("sst", fitResult.sst); READ_DOUBLE_VALUE("rms", fitResult.rms); READ_DOUBLE_VALUE("rsd", fitResult.rsd); READ_DOUBLE_VALUE("mse", fitResult.mse); READ_DOUBLE_VALUE("rmse", fitResult.rmse); READ_DOUBLE_VALUE("mae", fitResult.mae); READ_DOUBLE_VALUE("rsquare", fitResult.rsquare); READ_DOUBLE_VALUE("rsquareAdj", fitResult.rsquareAdj); READ_DOUBLE_VALUE("chisq_p", fitResult.chisq_p); READ_DOUBLE_VALUE("fdist_F", fitResult.fdist_F); READ_DOUBLE_VALUE("fdist_p", fitResult.fdist_p); READ_DOUBLE_VALUE("aic", fitResult.aic); READ_DOUBLE_VALUE("bic", fitResult.bic); READ_STRING_VALUE("solverOutput", fitResult.solverOutput); } else if (reader->name() == "column") { Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } DEBUG("############################ reading column " << STDSTRING(column->name())) if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; else if (column->name() == "residuals") d->residualsColumn = column; } } // older model save the param names also for non-custom models: remove them while (d->fitData.paramNames.size() > d->fitData.paramStartValues.size()) d->fitData.paramNames.removeLast(); if (d->fitData.paramNamesUtf8.isEmpty()) d->fitData.paramNamesUtf8 << d->fitData.paramNames; DEBUG("# params = " << d->fitData.paramNames.size()); if (preview) return true; // new fit model style (reset model type of old projects) if (d->fitData.modelCategory == nsl_fit_model_basic && d->fitData.modelType >= NSL_FIT_MODEL_BASIC_COUNT) { DEBUG("RESET old fit model"); d->fitData.modelType = 0; d->fitData.degree = 1; d->fitData.paramNames.clear(); d->fitData.paramNamesUtf8.clear(); // reset size of fields not touched by initFitData() d->fitData.paramStartValues.resize(2); d->fitData.paramFixed.resize(2); d->fitResult.paramValues.resize(2); d->fitResult.errorValues.resize(2); d->fitResult.tdist_tValues.resize(2); d->fitResult.tdist_pValues.resize(2); d->fitResult.tdist_marginValues.resize(2); } // not present in old projects int np = d->fitResult.paramValues.size(); if (d->fitResult.tdist_tValues.size() == 0) d->fitResult.tdist_tValues.resize(np); if (d->fitResult.tdist_pValues.size() == 0) d->fitResult.tdist_pValues.resize(np); if (d->fitResult.tdist_marginValues.size() == 0) d->fitResult.tdist_marginValues.resize(np); DEBUG("# start values = " << d->fitData.paramStartValues.size()); // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); addChild(d->residualsColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); d->residualsVector = static_cast* >(d->residualsColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } + DEBUG("XYFitCurve::load() DONE"); return true; } diff --git a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp index c90ae7fee..49faa722f 100644 --- a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp @@ -1,1426 +1,1430 @@ /*************************************************************************** File : XYFitCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2014-2020 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : widget for editing properties of fit curves ***************************************************************************/ /*************************************************************************** * * * 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 2 of the License, 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; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYFitCurveDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/lib/macros.h" #include "backend/gsl/ExpressionParser.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/widgets/ConstantsWidget.h" #include "kdefrontend/widgets/FunctionsWidget.h" #include "kdefrontend/widgets/FitOptionsWidget.h" #include "kdefrontend/widgets/FitParametersWidget.h" #include #include #include #include #include #include extern "C" { #include "backend/nsl/nsl_sf_stats.h" } /*! \class XYFitCurveDock \brief Provides a widget for editing the properties of the XYFitCurves (2D-curves defined by a fit model) currently selected in the project explorer. If more then one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYFitCurveDock::XYFitCurveDock(QWidget* parent) : XYCurveDock(parent) { //remove the tab "Error bars" ui.tabWidget->removeTab(5); } /*! * set up "General" tab */ void XYFitCurveDock::setupGeneral() { DEBUG("XYFitCurveDock::setupGeneral()"); QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); m_leName = uiGeneralTab.leName; m_leComment = uiGeneralTab.leComment; auto* gridLayout = static_cast(generalTab->layout()); gridLayout->setContentsMargins(2, 2, 2, 2); gridLayout->setHorizontalSpacing(2); gridLayout->setVerticalSpacing(2); uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); cbDataSourceCurve = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbDataSourceCurve, 5, 3, 1, 4); cbXDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXDataColumn, 6, 3, 1, 4); cbXErrorColumn = new TreeViewComboBox(generalTab); cbXErrorColumn->setEnabled(false); uiGeneralTab.hlXError->addWidget(cbXErrorColumn); cbYDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYDataColumn, 7, 3, 1, 4); cbYErrorColumn = new TreeViewComboBox(generalTab); cbYErrorColumn->setEnabled(false); uiGeneralTab.hlYWeight->addWidget(cbYErrorColumn); // X/Y-Weight for (int i = 0; i < NSL_FIT_WEIGHT_TYPE_COUNT; i++) { uiGeneralTab.cbXWeight->addItem(nsl_fit_weight_type_name[i]); uiGeneralTab.cbYWeight->addItem(nsl_fit_weight_type_name[i]); } uiGeneralTab.cbXWeight->setCurrentIndex(nsl_fit_weight_no); uiGeneralTab.cbYWeight->setCurrentIndex(nsl_fit_weight_no); for (int i = 0; i < NSL_FIT_MODEL_CATEGORY_COUNT; i++) uiGeneralTab.cbCategory->addItem(nsl_fit_model_category_name[i]); uiGeneralTab.teEquation->setMaximumHeight(uiGeneralTab.leName->sizeHint().height() * 2); fitParametersWidget = new FitParametersWidget(uiGeneralTab.frameParameters); auto* l = new QVBoxLayout(); l->setContentsMargins(0, 0, 0, 0); l->addWidget(fitParametersWidget); uiGeneralTab.frameParameters->setLayout(l); //use white background in the preview label QPalette p; p.setColor(QPalette::Window, Qt::white); uiGeneralTab.lFuncPic->setAutoFillBackground(true); uiGeneralTab.lFuncPic->setPalette(p); uiGeneralTab.tbConstants->setIcon(QIcon::fromTheme("labplot-format-text-symbol")); uiGeneralTab.tbFunctions->setIcon(QIcon::fromTheme("preferences-desktop-font")); uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build")); // TODO: setting checked background color to unchecked color // p = uiGeneralTab.lData->palette(); // QWidget::palette().color(QWidget::backgroundRole()) // not working with 'transparent' // p.setColor(QPalette::Base, Qt::transparent); // uiGeneralTab.lData->setPalette(p); // see https://forum.qt.io/topic/41325/solved-background-of-checked-qpushbutton-with-stylesheet/2 // Styles not usable (here: text color not theme dependent). see https://forum.qt.io/topic/60546/qpushbutton-default-windows-style-sheet/9 // uiGeneralTab.lData->setStyleSheet("QToolButton:checked{background-color: transparent;border: 3px transparent;padding: 3px;}"); // uiGeneralTab.lData->setAutoFillBackground(true); uiGeneralTab.twLog->setEditTriggers(QAbstractItemView::NoEditTriggers); uiGeneralTab.twParameters->setEditTriggers(QAbstractItemView::NoEditTriggers); uiGeneralTab.twGoodness->setEditTriggers(QAbstractItemView::NoEditTriggers); //don't allow word wrapping in the log-table for the multi-line iterations string uiGeneralTab.twLog->setWordWrap(false); // show all options per default showDataOptions(true); showFitOptions(true); showWeightsOptions(true); showParameters(true); showResults(true); // context menus uiGeneralTab.twParameters->setContextMenuPolicy(Qt::CustomContextMenu); uiGeneralTab.twGoodness->setContextMenuPolicy(Qt::CustomContextMenu); uiGeneralTab.twLog->setContextMenuPolicy(Qt::CustomContextMenu); connect(uiGeneralTab.twParameters, &QTableWidget::customContextMenuRequested, this, &XYFitCurveDock::resultParametersContextMenuRequest); connect(uiGeneralTab.twGoodness, &QTableWidget::customContextMenuRequested, this, &XYFitCurveDock::resultGoodnessContextMenuRequest); connect(uiGeneralTab.twLog, &QTableWidget::customContextMenuRequested, this, &XYFitCurveDock::resultLogContextMenuRequest); uiGeneralTab.twLog->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); uiGeneralTab.twGoodness->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); uiGeneralTab.twGoodness->item(0, 1)->setText(UTF8_QSTRING("χ²")); uiGeneralTab.twGoodness->item(1, 1)->setText(i18n("reduced") + ' ' + UTF8_QSTRING("χ²") + " (" + UTF8_QSTRING("χ²") + "/dof)"); uiGeneralTab.twGoodness->item(3, 1)->setText(UTF8_QSTRING("R²")); uiGeneralTab.twGoodness->item(4, 1)->setText(UTF8_QSTRING("R̄²")); uiGeneralTab.twGoodness->item(5, 0)->setText(UTF8_QSTRING("χ²") + ' ' + i18n("test")); uiGeneralTab.twGoodness->item(5, 1)->setText("P > " + UTF8_QSTRING("χ²")); auto* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); //Slots connect(uiGeneralTab.leName, &QLineEdit::textChanged, this, &XYFitCurveDock::nameChanged); connect(uiGeneralTab.leComment, &QLineEdit::textChanged, this, &XYFitCurveDock::commentChanged); connect(uiGeneralTab.chkVisible, &QCheckBox::clicked, this, &XYFitCurveDock::visibilityChanged); connect(uiGeneralTab.cbDataSourceType, QOverload::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::dataSourceTypeChanged); connect(uiGeneralTab.lWeights, &QPushButton::clicked, this, &XYFitCurveDock::showWeightsOptions); connect(uiGeneralTab.cbXWeight, QOverload::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::xWeightChanged); connect(uiGeneralTab.cbYWeight, QOverload::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::yWeightChanged); connect(uiGeneralTab.cbCategory, QOverload::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::categoryChanged); connect(uiGeneralTab.cbModel, QOverload::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::modelTypeChanged); connect(uiGeneralTab.sbDegree, QOverload::of(&QSpinBox::valueChanged), this, &XYFitCurveDock::updateModelEquation); connect(uiGeneralTab.teEquation, &ExpressionTextEdit::expressionChanged, this, &XYFitCurveDock::expressionChanged); connect(uiGeneralTab.tbConstants, &QToolButton::clicked, this, &XYFitCurveDock::showConstants); connect(uiGeneralTab.tbFunctions, &QToolButton::clicked, this, &XYFitCurveDock::showFunctions); connect(uiGeneralTab.pbOptions, &QPushButton::clicked, this, &XYFitCurveDock::showOptions); connect(uiGeneralTab.pbRecalculate, &QPushButton::clicked, this, &XYFitCurveDock::recalculateClicked); connect(uiGeneralTab.lData, &QPushButton::clicked, this, &XYFitCurveDock::showDataOptions); connect(uiGeneralTab.lFit, &QPushButton::clicked, this, &XYFitCurveDock::showFitOptions); connect(uiGeneralTab.lParameters, &QPushButton::clicked, this, &XYFitCurveDock::showParameters); connect(uiGeneralTab.lResults, &QPushButton::clicked, this, &XYFitCurveDock::showResults); connect(cbDataSourceCurve, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::dataSourceCurveChanged); connect(cbXDataColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::xDataColumnChanged); connect(cbYDataColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::yDataColumnChanged); connect(cbXErrorColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::xErrorColumnChanged); connect(cbYErrorColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::yErrorColumnChanged); } /* * load curve settings */ void XYFitCurveDock::initGeneralTab() { DEBUG("XYFitCurveDock::initGeneralTab()"); //if there are more then one curve in the list, disable the tab "general" if (m_curvesList.size() == 1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); } else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.leName->setText(QString()); uiGeneralTab.leComment->setText(QString()); } auto* fitCurve = static_cast(m_curve); checkColumnAvailability(cbXDataColumn, fitCurve->xDataColumn(), fitCurve->xDataColumnPath()); checkColumnAvailability(cbYDataColumn, fitCurve->yDataColumn(), fitCurve->yDataColumnPath()); checkColumnAvailability(cbXErrorColumn, fitCurve->xErrorColumn(), fitCurve->xErrorColumnPath()); checkColumnAvailability(cbYErrorColumn, fitCurve->yErrorColumn(), fitCurve->yErrorColumnPath()); uiGeneralTab.cbDataSourceType->setCurrentIndex(m_fitCurve->dataSourceType()); this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_fitCurve->dataSourceCurve()); XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_fitCurve->xDataColumn()); XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_fitCurve->yDataColumn()); XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, m_fitCurve->xErrorColumn()); XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, m_fitCurve->yErrorColumn()); int tmpModelType = m_fitData.modelType; // save type because it's reset when category changes if (m_fitData.modelCategory == nsl_fit_model_custom) uiGeneralTab.cbCategory->setCurrentIndex(uiGeneralTab.cbCategory->count() - 1); else uiGeneralTab.cbCategory->setCurrentIndex(m_fitData.modelCategory); categoryChanged(m_fitData.modelCategory); // fill model types m_fitData.modelType = tmpModelType; if (m_fitData.modelCategory != nsl_fit_model_custom) uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); uiGeneralTab.cbXWeight->setCurrentIndex(m_fitData.xWeightsType); uiGeneralTab.cbYWeight->setCurrentIndex(m_fitData.yWeightsType); uiGeneralTab.sbDegree->setValue(m_fitData.degree); if (m_fitData.paramStartValues.size() > 0) DEBUG(" start value 1 = " << m_fitData.paramStartValues.at(0)); DEBUG(" model degree = " << m_fitData.degree); this->showFitResult(); uiGeneralTab.chkVisible->setChecked(m_curve->isVisible()); //Slots connect(m_fitCurve, &XYFitCurve::aspectDescriptionChanged, this, &XYFitCurveDock::curveDescriptionChanged); connect(m_fitCurve, &XYFitCurve::dataSourceTypeChanged, this, &XYFitCurveDock::curveDataSourceTypeChanged); connect(m_fitCurve, &XYFitCurve::dataSourceCurveChanged, this, &XYFitCurveDock::curveDataSourceCurveChanged); connect(m_fitCurve, &XYFitCurve::xDataColumnChanged, this, &XYFitCurveDock::curveXDataColumnChanged); connect(m_fitCurve, &XYFitCurve::yDataColumnChanged, this, &XYFitCurveDock::curveYDataColumnChanged); connect(m_fitCurve, &XYFitCurve::xErrorColumnChanged, this, &XYFitCurveDock::curveXErrorColumnChanged); connect(m_fitCurve, &XYFitCurve::yErrorColumnChanged, this, &XYFitCurveDock::curveYErrorColumnChanged); connect(m_fitCurve, &XYFitCurve::fitDataChanged, this, &XYFitCurveDock::curveFitDataChanged); connect(m_fitCurve, &XYFitCurve::sourceDataChanged, this, &XYFitCurveDock::enableRecalculate); connect(fitParametersWidget, &FitParametersWidget::parametersChanged, this, &XYFitCurveDock::parametersChanged); connect(fitParametersWidget, &FitParametersWidget::parametersValid, this, &XYFitCurveDock::parametersValid); + DEBUG("XYFitCurveDock::initGeneralTab() DONE"); } void XYFitCurveDock::setModel() { DEBUG("XYFitCurveDock::setModel()"); QList list{AspectType::Folder, AspectType::Datapicker, AspectType::Worksheet, AspectType::CartesianPlot, AspectType::XYCurve}; cbDataSourceCurve->setTopLevelClasses(list); QList hiddenAspects; for (auto* curve : m_curvesList) hiddenAspects << curve; cbDataSourceCurve->setHiddenAspects(hiddenAspects); list = {AspectType::Folder, AspectType::Workbook, AspectType::Spreadsheet, AspectType::LiveDataSource, AspectType::Column, AspectType::CantorWorksheet, AspectType::Datapicker}; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbXErrorColumn->setTopLevelClasses(list); cbYErrorColumn->setTopLevelClasses(list); cbDataSourceCurve->setModel(m_aspectTreeModel); cbXDataColumn->setModel(m_aspectTreeModel); cbYDataColumn->setModel(m_aspectTreeModel); cbXErrorColumn->setModel(m_aspectTreeModel); cbYErrorColumn->setModel(m_aspectTreeModel); XYCurveDock::setModel(); + DEBUG("XYFitCurveDock::setModel() DONE"); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYFitCurveDock::setCurves(QList list) { DEBUG("XYFitCurveDock::setCurves()"); m_initializing = true; m_curvesList = list; m_curve = list.first(); m_aspect = m_curve; m_fitCurve = dynamic_cast(m_curve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); this->setModel(); m_fitData = m_fitCurve->fitData(); if (m_fitData.paramStartValues.size() > 0) { DEBUG(" start value 1 = " << m_fitData.paramStartValues.at(0)); } DEBUG(" model degree = " << m_fitData.degree); DEBUG(" # params = " << m_fitData.paramNames.size()); DEBUG(" # start values = " << m_fitData.paramStartValues.size()); fitParametersWidget->setFitData(&m_fitData); initGeneralTab(); initTabs(); enableRecalculate(); m_initializing = false; //init parameter list when not available if (m_fitData.paramStartValues.size() == 0) updateModelEquation(); } //************************************************************* //**** SLOTs for changes triggered in XYFitCurveDock ***** //************************************************************* void XYFitCurveDock::dataSourceTypeChanged(int index) { const auto type = (XYAnalysisCurve::DataSourceType)index; if (type == XYAnalysisCurve::DataSourceSpreadsheet) { uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); uiGeneralTab.lXColumn->show(); cbXDataColumn->show(); uiGeneralTab.lYColumn->show(); cbYDataColumn->show(); } else { uiGeneralTab.lDataSourceCurve->show(); cbDataSourceCurve->show(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) dynamic_cast(curve)->setDataSourceType(type); } void XYFitCurveDock::dataSourceCurveChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* dataSourceCurve = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setDataSourceCurve(dataSourceCurve); } void XYFitCurveDock::xDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setXDataColumn(column); // set model dependent start values from new data XYFitCurve::initStartValues(m_fitData, m_curve); // update model limits depending on number of points modelTypeChanged(uiGeneralTab.cbModel->currentIndex()); cbXDataColumn->useCurrentIndexText(true); cbXDataColumn->setInvalid(false); } void XYFitCurveDock::yDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setYDataColumn(column); // set model dependent start values from new data XYFitCurve::initStartValues(m_fitData, m_curve); cbYDataColumn->useCurrentIndexText(true); cbYDataColumn->setInvalid(false); } void XYFitCurveDock::xErrorColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setXErrorColumn(column); cbXErrorColumn->useCurrentIndexText(true); cbXErrorColumn->setInvalid(false); } void XYFitCurveDock::yErrorColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setYErrorColumn(column); cbYErrorColumn->useCurrentIndexText(true); cbYErrorColumn->setInvalid(false); } ///////////////////////// fold/unfold options ////////////////////////////////////////////////// void XYFitCurveDock::showDataOptions(bool checked) { if (checked) { uiGeneralTab.lData->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.lDataSourceType->show(); uiGeneralTab.cbDataSourceType->show(); // select options for current source type dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); } else { uiGeneralTab.lData->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.lDataSourceType->hide(); uiGeneralTab.cbDataSourceType->hide(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); } } void XYFitCurveDock::showWeightsOptions(bool checked) { if (checked) { uiGeneralTab.lWeights->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.lXWeight->show(); uiGeneralTab.cbXWeight->show(); uiGeneralTab.lXErrorCol->show(); cbXErrorColumn->show(); uiGeneralTab.lYWeight->show(); uiGeneralTab.cbYWeight->show(); uiGeneralTab.lYErrorCol->show(); cbYErrorColumn->show(); } else { uiGeneralTab.lWeights->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.lXWeight->hide(); uiGeneralTab.cbXWeight->hide(); uiGeneralTab.lXErrorCol->hide(); cbXErrorColumn->hide(); uiGeneralTab.lYWeight->hide(); uiGeneralTab.cbYWeight->hide(); uiGeneralTab.lYErrorCol->hide(); cbYErrorColumn->hide(); } } void XYFitCurveDock::showFitOptions(bool checked) { if (checked) { uiGeneralTab.lFit->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.lCategory->show(); uiGeneralTab.cbCategory->show(); uiGeneralTab.lModel->show(); uiGeneralTab.cbModel->show(); uiGeneralTab.lEquation->show(); m_initializing = true; // do not change start parameter modelTypeChanged(uiGeneralTab.cbModel->currentIndex()); m_initializing = false; } else { uiGeneralTab.lFit->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.lCategory->hide(); uiGeneralTab.cbCategory->hide(); uiGeneralTab.lModel->hide(); uiGeneralTab.cbModel->hide(); uiGeneralTab.lDegree->hide(); uiGeneralTab.sbDegree->hide(); uiGeneralTab.lEquation->hide(); uiGeneralTab.lFuncPic->hide(); uiGeneralTab.teEquation->hide(); uiGeneralTab.tbFunctions->hide(); uiGeneralTab.tbConstants->hide(); } } void XYFitCurveDock::showParameters(bool checked) { if (checked) { uiGeneralTab.lParameters->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.frameParameters->show(); } else { uiGeneralTab.lParameters->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.frameParameters->hide(); } } void XYFitCurveDock::showResults(bool checked) { if (checked) { uiGeneralTab.lResults->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.twResults->show(); } else { uiGeneralTab.lResults->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.twResults->hide(); } } /////////////////////////////////////////////////////////////////////////// void XYFitCurveDock::xWeightChanged(int index) { DEBUG("xWeightChanged() weight = " << nsl_fit_weight_type_name[index]); m_fitData.xWeightsType = (nsl_fit_weight_type)index; // enable/disable weight column switch ((nsl_fit_weight_type)index) { case nsl_fit_weight_no: case nsl_fit_weight_statistical: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative: case nsl_fit_weight_relative_fit: cbXErrorColumn->setEnabled(false); uiGeneralTab.lXErrorCol->setEnabled(false); break; case nsl_fit_weight_instrumental: case nsl_fit_weight_direct: case nsl_fit_weight_inverse: cbXErrorColumn->setEnabled(true); uiGeneralTab.lXErrorCol->setEnabled(true); break; } enableRecalculate(); } void XYFitCurveDock::yWeightChanged(int index) { DEBUG("yWeightChanged() weight = " << nsl_fit_weight_type_name[index]); m_fitData.yWeightsType = (nsl_fit_weight_type)index; // enable/disable weight column switch ((nsl_fit_weight_type)index) { case nsl_fit_weight_no: case nsl_fit_weight_statistical: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative: case nsl_fit_weight_relative_fit: cbYErrorColumn->setEnabled(false); uiGeneralTab.lYErrorCol->setEnabled(false); break; case nsl_fit_weight_instrumental: case nsl_fit_weight_direct: case nsl_fit_weight_inverse: cbYErrorColumn->setEnabled(true); uiGeneralTab.lYErrorCol->setEnabled(true); break; } enableRecalculate(); } /*! * called when the fit model category (basic functions, peak functions etc.) was changed. * In the combobox for the model type shows the model types for the current category \index and calls \c modelTypeChanged() * to update the model type dependent widgets in the general-tab. */ void XYFitCurveDock::categoryChanged(int index) { if (index == nsl_fit_model_custom) { DEBUG("categoryChanged() category = \"nsl_fit_model_custom\""); } else { DEBUG("categoryChanged() category = \"" << nsl_fit_model_category_name[index] << "\""); } bool hasChanged = true; // nothing has changed when ... if (m_fitData.modelCategory == (nsl_fit_model_category)index || (m_fitData.modelCategory == nsl_fit_model_custom && index == uiGeneralTab.cbCategory->count() - 1) ) hasChanged = false; if (uiGeneralTab.cbCategory->currentIndex() == uiGeneralTab.cbCategory->count() - 1) m_fitData.modelCategory = nsl_fit_model_custom; else m_fitData.modelCategory = (nsl_fit_model_category)index; uiGeneralTab.cbModel->clear(); uiGeneralTab.cbModel->show(); uiGeneralTab.lModel->show(); switch (m_fitData.modelCategory) { case nsl_fit_model_basic: for (int i = 0; i < NSL_FIT_MODEL_BASIC_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_basic_name[i]); break; case nsl_fit_model_peak: { for (int i = 0; i < NSL_FIT_MODEL_PEAK_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_peak_name[i]); #if defined(_MSC_VER) // disable voigt model const QStandardItemModel* model = qobject_cast(uiGeneralTab.cbModel->model()); QStandardItem* item = model->item(nsl_fit_model_voigt); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); #endif break; } case nsl_fit_model_growth: for (int i = 0; i < NSL_FIT_MODEL_GROWTH_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_growth_name[i]); break; case nsl_fit_model_distribution: { for (int i = 0; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_sf_stats_distribution_name[i]); // not-used items are disabled here const auto* model = qobject_cast(uiGeneralTab.cbModel->model()); for (int i = 1; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) { // unused distributions if (i == nsl_sf_stats_levy_alpha_stable || i == nsl_sf_stats_levy_skew_alpha_stable || i == nsl_sf_stats_bernoulli) { QStandardItem* item = model->item(i); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); } } break; } case nsl_fit_model_custom: uiGeneralTab.cbModel->addItem(i18n("Custom")); uiGeneralTab.cbModel->hide(); uiGeneralTab.lModel->hide(); } if (hasChanged) { //show the fit-model for the currently selected default (first) fit-model uiGeneralTab.cbModel->setCurrentIndex(0); uiGeneralTab.sbDegree->setValue(1); // when model type does not change, call it here updateModelEquation(); } enableRecalculate(); } /*! * called when the fit model type (depends on category) was changed. * Updates the model type dependent widgets in the general-tab and calls \c updateModelEquation() to update the preview pixmap. */ void XYFitCurveDock::modelTypeChanged(int index) { DEBUG("modelTypeChanged() type = " << (unsigned int)index << ", initializing = " << m_initializing << ", current type = " << m_fitData.modelType); // leave if there is no selection if (index == -1) return; bool custom = false; if (m_fitData.modelCategory == nsl_fit_model_custom) custom = true; uiGeneralTab.teEquation->setReadOnly(!custom); uiGeneralTab.lModel->setVisible(!custom); uiGeneralTab.cbModel->setVisible(!custom); uiGeneralTab.tbFunctions->setVisible(custom); uiGeneralTab.tbConstants->setVisible(custom); // default settings uiGeneralTab.lDegree->setText(i18n("Degree:")); if (m_fitData.modelType != index) uiGeneralTab.sbDegree->setValue(1); const AbstractColumn* xColumn = nullptr; if (m_fitCurve->dataSourceType() == XYAnalysisCurve::DataSourceSpreadsheet) { DEBUG(" data source: Spreadsheet") //auto* aspect = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); //xColumn = dynamic_cast(aspect); xColumn = m_fitCurve->xDataColumn(); } else { DEBUG(" data source: Curve") if (m_fitCurve->dataSourceCurve() != nullptr) xColumn = m_fitCurve->dataSourceCurve()->xColumn(); } // with no xColumn: show all models (assume 100 data points) const int availableRowCount = (xColumn != nullptr) ? xColumn->availableRowCount() : 100; DEBUG(" available row count = " << availableRowCount) bool disableFit = false; switch (m_fitData.modelCategory) { case nsl_fit_model_basic: switch (index) { case nsl_fit_model_polynomial: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(qMin(availableRowCount - 1, 10)); break; case nsl_fit_model_fourier: if (availableRowCount < 4) { // too few data points uiGeneralTab.lDegree->setVisible(false); uiGeneralTab.sbDegree->setVisible(false); disableFit = true; } else { uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(qMin(availableRowCount/2 - 1, 10)); } break; case nsl_fit_model_power: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(2); //TODO: limit degree depending on availableRowCount break; case nsl_fit_model_exponential: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(10); //TODO: limit degree depending on availableRowCount break; default: uiGeneralTab.lDegree->setVisible(false); uiGeneralTab.sbDegree->setVisible(false); } break; case nsl_fit_model_peak: // all models support multiple peaks uiGeneralTab.lDegree->setText(i18n("Number of peaks:")); uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(9); break; case nsl_fit_model_growth: case nsl_fit_model_distribution: case nsl_fit_model_custom: uiGeneralTab.lDegree->setVisible(false); uiGeneralTab.sbDegree->setVisible(false); } m_fitData.modelType = index; updateModelEquation(); if (disableFit) uiGeneralTab.pbRecalculate->setEnabled(false); } /*! * Show the preview pixmap of the fit model expression for the current model category and type. * Called when the model type or the degree of the model were changed. */ void XYFitCurveDock::updateModelEquation() { if (m_fitData.modelCategory == nsl_fit_model_custom) { DEBUG("XYFitCurveDock::updateModelEquation() category = nsl_fit_model_custom, type = " << m_fitData.modelType); } else { DEBUG("XYFitCurveDock::updateModelEquation() category = " << nsl_fit_model_category_name[m_fitData.modelCategory] << ", type = " << m_fitData.modelType); } //this function can also be called when the value for the degree was changed -> update the fit data structure int degree = uiGeneralTab.sbDegree->value(); if (!m_initializing) { m_fitData.degree = degree; XYFitCurve::initFitData(m_fitData); // set model dependent start values from curve data XYFitCurve::initStartValues(m_fitData, m_curve); // udpate parameter widget fitParametersWidget->setFitData(&m_fitData); } // variables/parameter that are known QStringList vars = {"x"}; vars << m_fitData.paramNames; uiGeneralTab.teEquation->setVariables(vars); // set formula picture uiGeneralTab.lEquation->setText(QLatin1String("f(x) =")); QString file; switch (m_fitData.modelCategory) { case nsl_fit_model_basic: { // formula pic depends on degree QString numSuffix = QString::number(degree); if (degree > 4) numSuffix = '4'; if ((nsl_fit_model_type_basic)m_fitData.modelType == nsl_fit_model_power && degree > 2) numSuffix = '2'; file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_basic_pic_name[m_fitData.modelType]) + numSuffix + ".png"); break; } case nsl_fit_model_peak: { // formula pic depends on number of peaks QString numSuffix = QString::number(degree); if (degree > 4) numSuffix = '4'; file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_peak_pic_name[m_fitData.modelType]) + numSuffix + ".png"); break; } case nsl_fit_model_growth: file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_growth_pic_name[m_fitData.modelType]) + ".png"); break; case nsl_fit_model_distribution: file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/gsl_distributions/" + QString(nsl_sf_stats_distribution_pic_name[m_fitData.modelType]) + ".png"); // change label if (m_fitData.modelType == nsl_sf_stats_poisson) uiGeneralTab.lEquation->setText(QLatin1String("f(k)/A =")); else uiGeneralTab.lEquation->setText(QLatin1String("f(x)/A =")); break; case nsl_fit_model_custom: uiGeneralTab.lFuncPic->hide(); uiGeneralTab.teEquation->show(); uiGeneralTab.teEquation->setPlainText(m_fitData.model); } if (m_fitData.modelCategory != nsl_fit_model_custom) { DEBUG("Model pixmap path = " << STDSTRING(file)); uiGeneralTab.lFuncPic->setPixmap(file); uiGeneralTab.lFuncPic->show(); uiGeneralTab.teEquation->hide(); } enableRecalculate(); } void XYFitCurveDock::showConstants() { QMenu menu; ConstantsWidget constants(&menu); connect(&constants, &ConstantsWidget::constantSelected, this, &XYFitCurveDock::insertConstant); connect(&constants, &ConstantsWidget::constantSelected, &menu, &QMenu::close); connect(&constants, &ConstantsWidget::canceled, &menu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&constants); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbConstants->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.tbConstants->mapToGlobal(pos)); } void XYFitCurveDock::showFunctions() { QMenu menu; FunctionsWidget functions(&menu); connect(&functions, &FunctionsWidget::functionSelected, this, &XYFitCurveDock::insertFunction); connect(&functions, &FunctionsWidget::functionSelected, &menu, &QMenu::close); connect(&functions, &FunctionsWidget::canceled, &menu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&functions); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbFunctions->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.tbFunctions->mapToGlobal(pos)); } /*! * Update parameter by parsing expression * Only called for custom fit model */ void XYFitCurveDock::updateParameterList() { DEBUG("XYFitCurveDock::updateParameterList()"); // use current model function m_fitData.model = uiGeneralTab.teEquation->toPlainText(); ExpressionParser* parser = ExpressionParser::getInstance(); QStringList vars; // variables that are known vars << "x"; //TODO: others? m_fitData.paramNames = m_fitData.paramNamesUtf8 = parser->getParameter(m_fitData.model, vars); // if number of parameter changed int oldNumberOfParameter = m_fitData.paramStartValues.size(); int numberOfParameter = m_fitData.paramNames.size(); DEBUG(" old number of parameter: " << oldNumberOfParameter << " new number of parameter: " << numberOfParameter); if (numberOfParameter != oldNumberOfParameter) { m_fitData.paramStartValues.resize(numberOfParameter); m_fitData.paramFixed.resize(numberOfParameter); m_fitData.paramLowerLimits.resize(numberOfParameter); m_fitData.paramUpperLimits.resize(numberOfParameter); } if (numberOfParameter > oldNumberOfParameter) { for (int i = oldNumberOfParameter; i < numberOfParameter; ++i) { m_fitData.paramStartValues[i] = 1.0; m_fitData.paramFixed[i] = false; m_fitData.paramLowerLimits[i] = -std::numeric_limits::max(); m_fitData.paramUpperLimits[i] = std::numeric_limits::max(); } } parametersChanged(); } /*! * called when parameter names and/or start values for the model were changed * also called from parameter widget */ void XYFitCurveDock::parametersChanged(bool updateParameterWidget) { DEBUG("XYFitCurveDock::parametersChanged() m_initializing = " << m_initializing); //parameter names were (probably) changed -> set the new names in EquationTextEdit uiGeneralTab.teEquation->setVariables(m_fitData.paramNames); if (m_initializing) return; if (updateParameterWidget) fitParametersWidget->setFitData(&m_fitData); enableRecalculate(); } void XYFitCurveDock::parametersValid(bool valid) { DEBUG("XYFitCurveDock::parametersValid() valid = " << valid); m_parametersValid = valid; } void XYFitCurveDock::showOptions() { QMenu menu; FitOptionsWidget w(&menu, &m_fitData, m_fitCurve); connect(&w, &FitOptionsWidget::finished, &menu, &QMenu::close); connect(&w, &FitOptionsWidget::optionsChanged, this, &XYFitCurveDock::enableRecalculate); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&w); menu.addAction(widgetAction); menu.setTearOffEnabled(true); //menu.setWindowFlags(menu.windowFlags() & Qt::MSWindowsFixedSizeDialogHint); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.pbOptions->width(), 0); menu.exec(uiGeneralTab.pbOptions->mapToGlobal(pos)); } void XYFitCurveDock::insertFunction(const QString& str) const { //TODO: not all function have only one argument! uiGeneralTab.teEquation->insertPlainText(str + "(x)"); } void XYFitCurveDock::insertConstant(const QString& str) const { uiGeneralTab.teEquation->insertPlainText(str); } /*! * When a custom evaluate range is specified, set the plot range too. */ void XYFitCurveDock::setPlotXRange() { if (m_fitData.autoEvalRange || m_curve == nullptr) return; auto* plot = dynamic_cast(m_curve->parentAspect()); if (plot != nullptr) { double rmin = m_fitData.evalRange.first(); double rmax = m_fitData.evalRange.last(); double extend = (rmax-rmin) * 0.05; // +/- 5 percent of range. may be < 0 if (extend != 0.) { // avoid zero range plot->setXMin(rmin - extend); plot->setXMax(rmax + extend); } } } void XYFitCurveDock::recalculateClicked() { DEBUG("XYFitCurveDock::recalculateClicked()"); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); m_fitData.degree = uiGeneralTab.sbDegree->value(); if (m_fitData.modelCategory == nsl_fit_model_custom) updateParameterList(); for (XYCurve* curve: m_curvesList) dynamic_cast(curve)->setFitData(m_fitData); m_fitCurve->recalculate(); setPlotXRange(); //update fitParametersWidget if (m_fitData.useResults) { DEBUG(" nr of param names = " << m_fitData.paramNames.size()) DEBUG(" size of start values = " << m_fitData.paramStartValues.size()) DEBUG(" size of param values = " << m_fitCurve->fitResult().paramValues.size()) if (m_fitCurve->fitResult().paramValues.size() > 0) { // may be 0 if fit fails for (int i = 0; i < m_fitData.paramNames.size(); i++) m_fitData.paramStartValues[i] = m_fitCurve->fitResult().paramValues.at(i); fitParametersWidget->setFitData(&m_fitData); } else { DEBUG(" WARNING: no fit result available!") } } this->showFitResult(); uiGeneralTab.pbRecalculate->setEnabled(false); emit info(i18n("Fit status: %1", m_fitCurve->fitResult().status)); QApplication::restoreOverrideCursor(); DEBUG("XYFitCurveDock::recalculateClicked() DONE"); } void XYFitCurveDock::expressionChanged() { DEBUG("XYFitCurveDock::expressionChanged()"); if (m_initializing) return; // update parameter list for custom model if (m_fitData.modelCategory == nsl_fit_model_custom) updateParameterList(); enableRecalculate(); } void XYFitCurveDock::enableRecalculate() { DEBUG("XYFitCurveDock::enableRecalculate()"); if (m_initializing || m_fitCurve == nullptr) return; //no fitting possible without the x- and y-data bool hasSourceData = false; if (m_fitCurve->dataSourceType() == XYAnalysisCurve::DataSourceSpreadsheet) { auto* aspectX = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); auto* aspectY = static_cast(cbYDataColumn->currentModelIndex().internalPointer()); hasSourceData = (aspectX != nullptr && aspectY != nullptr); if (aspectX) { cbXDataColumn->useCurrentIndexText(true); cbXDataColumn->setInvalid(false); } if (aspectY) { cbYDataColumn->useCurrentIndexText(true); cbYDataColumn->setInvalid(false); } } else { hasSourceData = (m_fitCurve->dataSourceCurve() != nullptr); } uiGeneralTab.pbRecalculate->setEnabled(hasSourceData && m_parametersValid); // PREVIEW as soon as recalculate is enabled (does not need source data) if (m_parametersValid && m_fitData.previewEnabled) { DEBUG(" EVALUATE WITH PREVIEW ENABLED"); // use recent fit data m_fitCurve->setFitData(m_fitData); // calculate fit function m_fitCurve->evaluate(true); setPlotXRange(); } else { DEBUG(" PREVIEW DISABLED"); } + DEBUG("XYFitCurveDock::enableRecalculate() DONE"); } void XYFitCurveDock::resultCopySelection() { QTableWidget* tw{nullptr}; int currentTab = uiGeneralTab.twResults->currentIndex(); DEBUG("current tab = " << currentTab); if (currentTab == 0) tw = uiGeneralTab.twParameters; else if (currentTab == 1) tw = uiGeneralTab.twGoodness; else if (currentTab == 2) tw = uiGeneralTab.twLog; else return; const QTableWidgetSelectionRange& range = tw->selectedRanges().constFirst(); QString str; for (int i = 0; i < range.rowCount(); ++i) { if (i > 0) str += '\n'; for (int j = 0; j < range.columnCount(); ++j) { if (j > 0) str += '\t'; str += tw->item(range.topRow() + i, range.leftColumn() + j)->text(); } } str += '\n'; QApplication::clipboard()->setText(str); DEBUG(STDSTRING(QApplication::clipboard()->text())); } void XYFitCurveDock::resultCopyAll() { const XYFitCurve::FitResult& fitResult = m_fitCurve->fitResult(); int currentTab = uiGeneralTab.twResults->currentIndex(); QString str; if (currentTab == 0) { str = i18n("Parameters:") + '\n'; const int np = fitResult.paramValues.size(); for (int i = 0; i < np; i++) { if (m_fitData.paramFixed.at(i)) str += m_fitData.paramNamesUtf8.at(i) + QString(" = ") + QString::number(fitResult.paramValues.at(i)) + '\n'; else { str += m_fitData.paramNamesUtf8.at(i) + QString(" = ") + QString::number(fitResult.paramValues.at(i)) + UTF8_QSTRING("±") + QString::number(fitResult.errorValues.at(i)) + " (" + QString::number(100.*fitResult.errorValues.at(i)/std::abs(fitResult.paramValues.at(i)), 'g', 3) + " %)\n"; const double margin = fitResult.tdist_marginValues.at(i); QString tdistValueString; if (fitResult.tdist_tValues.at(i) < std::numeric_limits::max()) tdistValueString = QString::number(fitResult.tdist_tValues.at(i), 'g', 3); else tdistValueString = UTF8_QSTRING("∞"); str += " (" + i18n("t statistic:") + ' ' + tdistValueString + ", " + i18n("p value:") + ' ' + QString::number(fitResult.tdist_pValues.at(i), 'g', 3) + ", " + i18n("conf. interval:") + ' '; if (std::abs(fitResult.tdist_tValues.at(i)) < 1.e6) { str += QString::number(fitResult.paramValues.at(i) - margin) + " .. " + QString::number(fitResult.paramValues.at(i) + margin) + ")\n"; } else { str += i18n("too small"); } } } } else if (currentTab == 1) { str = i18n("Goodness of fit:") + '\n'; str += i18n("sum of squared residuals") + " (" + UTF8_QSTRING("χ²") + "): " + QString::number(fitResult.sse) + '\n'; if (fitResult.dof != 0) { str += i18n("reduced") + ' ' + UTF8_QSTRING("χ²") + ": " + QString::number(fitResult.rms) + '\n'; str += i18n("root mean square error") + " (RMSE): " + QString::number(fitResult.rsd) + '\n'; str += i18n("coefficient of determination") + " (" + UTF8_QSTRING("R²") + "): " + QString::number(fitResult.rsquare, 'g', 15) + '\n'; str += i18n("adj. coefficient of determination")+ " (" + UTF8_QSTRING("R̄²") + "): " + QString::number(fitResult.rsquareAdj, 'g', 15) + "\n\n"; str += i18n("P > ") + UTF8_QSTRING("χ²") + ": " + QString::number(fitResult.chisq_p, 'g', 3) + '\n'; str += i18n("F statistic") + ": " + QString::number(fitResult.fdist_F, 'g', 3) + '\n'; str += i18n("P > F") + ": " + QString::number(fitResult.fdist_p, 'g', 3) + '\n'; } str += i18n("mean absolute error:") + ' ' + QString::number(fitResult.mae) + '\n'; str += i18n("Akaike information criterion:") + ' ' + QString::number(fitResult.aic) + '\n'; str += i18n("Bayesian information criterion:") + ' ' + QString::number(fitResult.bic) + '\n'; } else if (currentTab == 2) { str = i18n("status:") + ' ' + fitResult.status + '\n'; str += i18n("iterations:") + ' ' + QString::number(fitResult.iterations) + '\n'; str += i18n("tolerance:") + ' ' + QString::number(m_fitData.eps) + '\n'; if (fitResult.elapsedTime > 1000) str += i18n("calculation time: %1 s", fitResult.elapsedTime/1000) + '\n'; else str += i18n("calculation time: %1 ms", fitResult.elapsedTime) + '\n'; str += i18n("degrees of freedom:") + ' ' + QString::number(fitResult.dof) + '\n'; str += i18n("number of parameters:") + ' ' + QString::number(fitResult.paramValues.size()) + '\n'; str += i18n("fit range:") + ' ' + QString::number(m_fitData.fitRange.first()) + " .. " + QString::number(m_fitData.fitRange.last()) + '\n'; str += i18n("Iterations:") + '\n'; for (const auto &s : m_fitData.paramNamesUtf8) str += s + '\t'; str += UTF8_QSTRING("χ²"); const QStringList iterations = fitResult.solverOutput.split(';'); for (const auto &s : iterations) if (!s.isEmpty()) str += '\n' + s; } QApplication::clipboard()->setText(str); DEBUG(STDSTRING(QApplication::clipboard()->text())); } void XYFitCurveDock::resultParametersContextMenuRequest(QPoint pos) { auto* contextMenu = new QMenu; contextMenu->addAction(i18n("Copy Selection"), this, &XYFitCurveDock::resultCopySelection); contextMenu->addAction(i18n("Copy All"), this, &XYFitCurveDock::resultCopyAll); contextMenu->exec(uiGeneralTab.twParameters->mapToGlobal(pos)); } void XYFitCurveDock::resultGoodnessContextMenuRequest(QPoint pos) { auto* contextMenu = new QMenu; contextMenu->addAction(i18n("Copy Selection"), this, &XYFitCurveDock::resultCopySelection); contextMenu->addAction(i18n("Copy All"), this, &XYFitCurveDock::resultCopyAll); contextMenu->exec(uiGeneralTab.twGoodness->mapToGlobal(pos)); } void XYFitCurveDock::resultLogContextMenuRequest(QPoint pos) { auto* contextMenu = new QMenu; contextMenu->addAction(i18n("Copy Selection"), this, &XYFitCurveDock::resultCopySelection); contextMenu->addAction(i18n("Copy All"), this, &XYFitCurveDock::resultCopyAll); contextMenu->exec(uiGeneralTab.twLog->mapToGlobal(pos)); } /*! * show the result and details of the fit */ void XYFitCurveDock::showFitResult() { DEBUG("XYFitCurveDock::showFitResult()"); //clear the previous result uiGeneralTab.twParameters->setRowCount(0); for (int row = 0; row < uiGeneralTab.twGoodness->rowCount(); ++row) uiGeneralTab.twGoodness->item(row, 2)->setText(QString()); for (int row = 0; row < uiGeneralTab.twLog->rowCount(); ++row) uiGeneralTab.twLog->item(row, 1)->setText(QString()); const XYFitCurve::FitResult& fitResult = m_fitCurve->fitResult(); if (!fitResult.available) { - DEBUG("fit result not available"); + DEBUG(" fit result not available"); return; } // Log uiGeneralTab.twLog->item(0, 1)->setText(fitResult.status); if (!fitResult.valid) { - DEBUG("fit result not valid"); + DEBUG(" fit result not valid"); return; } uiGeneralTab.twLog->item(1, 1)->setText(QString::number(fitResult.iterations)); uiGeneralTab.twLog->item(2, 1)->setText(QString::number(m_fitData.eps)); if (fitResult.elapsedTime > 1000) uiGeneralTab.twLog->item(3, 1)->setText(QString::number(fitResult.elapsedTime/1000) + " s"); else uiGeneralTab.twLog->item(3, 1)->setText(QString::number(fitResult.elapsedTime) + " ms"); uiGeneralTab.twLog->item(4, 1)->setText(QString::number(fitResult.dof)); uiGeneralTab.twLog->item(5, 1)->setText(QString::number(fitResult.paramValues.size())); uiGeneralTab.twLog->item(6, 1)->setText(QString::number(m_fitData.fitRange.first()) + " .. " + QString::number(m_fitData.fitRange.last()) ); // show all iterations QString str; for (const auto &s : m_fitData.paramNamesUtf8) str += s + '\t'; str += UTF8_QSTRING("χ²"); const QStringList iterations = fitResult.solverOutput.split(';'); for (const auto &s : iterations) if (!s.isEmpty()) str += '\n' + s; uiGeneralTab.twLog->item(7, 1)->setText(str); uiGeneralTab.twLog->resizeRowsToContents(); // Parameters const int np = m_fitData.paramNames.size(); uiGeneralTab.twParameters->setRowCount(np); QStringList headerLabels; headerLabels << i18n("Name") << i18n("Value") << i18n("Error") << i18n("Error, %") << i18n("t statistic") << QLatin1String("P > |t|") << i18n("Conf. Interval"); uiGeneralTab.twParameters->setHorizontalHeaderLabels(headerLabels); for (int i = 0; i < np; i++) { const double paramValue = fitResult.paramValues.at(i); const double errorValue = fitResult.errorValues.at(i); auto* item = new QTableWidgetItem(m_fitData.paramNamesUtf8.at(i)); item->setBackground(QApplication::palette().color(QPalette::Window)); uiGeneralTab.twParameters->setItem(i, 0, item); item = new QTableWidgetItem(QString::number(paramValue)); uiGeneralTab.twParameters->setItem(i, 1, item); if (!m_fitData.paramFixed.at(i)) { if (!std::isnan(errorValue)) { item = new QTableWidgetItem(QString::number(errorValue, 'g', 6)); uiGeneralTab.twParameters->setItem(i, 2, item); item = new QTableWidgetItem(QString::number(100.*errorValue/std::abs(paramValue), 'g', 3)); uiGeneralTab.twParameters->setItem(i, 3, item); } else { item = new QTableWidgetItem(UTF8_QSTRING("∞")); uiGeneralTab.twParameters->setItem(i, 2, item); item = new QTableWidgetItem(UTF8_QSTRING("∞")); uiGeneralTab.twParameters->setItem(i, 3, item); } // t values QString tdistValueString; if (fitResult.tdist_tValues.at(i) < std::numeric_limits::max()) tdistValueString = QString::number(fitResult.tdist_tValues.at(i), 'g', 3); else tdistValueString = UTF8_QSTRING("∞"); item = new QTableWidgetItem(tdistValueString); uiGeneralTab.twParameters->setItem(i, 4, item); // p values const double p = fitResult.tdist_pValues.at(i); item = new QTableWidgetItem(QString::number(p, 'g', 3)); // color p values depending on value if (p > 0.05) item->setForeground(QBrush(QApplication::palette().color(QPalette::LinkVisited))); else if (p > 0.01) item->setForeground(QBrush(Qt::darkGreen)); else if (p > 0.001) item->setForeground(QBrush(Qt::darkCyan)); else if (p > 0.0001) item->setForeground(QBrush(QApplication::palette().color(QPalette::Link))); else item->setForeground(QBrush(QApplication::palette().color(QPalette::Highlight))); uiGeneralTab.twParameters->setItem(i, 5, item); // Conf. interval if (!std::isnan(errorValue)) { const double margin = fitResult.tdist_marginValues.at(i); if (fitResult.tdist_tValues.at(i) < 1.e6) item = new QTableWidgetItem(QString::number(paramValue - margin) + QLatin1String(" .. ") + QString::number(paramValue + margin)); else item = new QTableWidgetItem(i18n("too small")); uiGeneralTab.twParameters->setItem(i, 6, item); } } } // Goodness of fit uiGeneralTab.twGoodness->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); uiGeneralTab.twGoodness->item(0, 2)->setText(QString::number(fitResult.sse)); if (fitResult.dof != 0) { uiGeneralTab.twGoodness->item(1, 2)->setText(QString::number(fitResult.rms)); uiGeneralTab.twGoodness->item(2, 2)->setText(QString::number(fitResult.rsd)); uiGeneralTab.twGoodness->item(3, 2)->setText(QString::number(fitResult.rsquare, 'g')); uiGeneralTab.twGoodness->item(4, 2)->setText(QString::number(fitResult.rsquareAdj, 'g')); // chi^2 and F test p-values uiGeneralTab.twGoodness->item(5, 2)->setText(QString::number(fitResult.chisq_p, 'g', 3)); uiGeneralTab.twGoodness->item(6, 2)->setText(QString::number(fitResult.fdist_F, 'g', 3)); uiGeneralTab.twGoodness->item(7, 2)->setText(QString::number(fitResult.fdist_p, 'g', 3)); uiGeneralTab.twGoodness->item(9, 2)->setText(QString::number(fitResult.aic, 'g', 3)); uiGeneralTab.twGoodness->item(10, 2)->setText(QString::number(fitResult.bic, 'g', 3)); } uiGeneralTab.twGoodness->item(8, 2)->setText(QString::number(fitResult.mae)); //resize the table headers to fit the new content uiGeneralTab.twLog->resizeColumnsToContents(); uiGeneralTab.twParameters->resizeColumnsToContents(); //twGoodness doesn't have any header -> resize sections uiGeneralTab.twGoodness->resizeColumnToContents(0); uiGeneralTab.twGoodness->resizeColumnToContents(1); uiGeneralTab.twGoodness->resizeColumnToContents(2); //enable the "recalculate"-button if the source data was changed since the last fit uiGeneralTab.pbRecalculate->setEnabled(m_fitCurve->isSourceDataChangedSinceLastRecalc()); + DEBUG("XYFitCurveDock::showFitResult() DONE"); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYFitCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) uiGeneralTab.leName->setText(aspect->name()); else if (aspect->comment() != uiGeneralTab.leComment->text()) uiGeneralTab.leComment->setText(aspect->comment()); m_initializing = false; } void XYFitCurveDock::curveDataSourceTypeChanged(XYAnalysisCurve::DataSourceType type) { m_initializing = true; uiGeneralTab.cbDataSourceType->setCurrentIndex(type); m_initializing = false; } void XYFitCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve); m_initializing = false; } void XYFitCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column); m_initializing = false; } void XYFitCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column); m_initializing = false; } void XYFitCurveDock::curveXErrorColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, column); m_initializing = false; } void XYFitCurveDock::curveYErrorColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, column); m_initializing = false; } /*! * called when fit data of fit curve changes */ void XYFitCurveDock::curveFitDataChanged(const XYFitCurve::FitData& fitData) { DEBUG("XYFitCurveDock::curveFitDataChanged()"); m_initializing = true; m_fitData = fitData; if (m_fitData.modelCategory != nsl_fit_model_custom) uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); uiGeneralTab.sbDegree->setValue(m_fitData.degree); m_initializing = false; } void XYFitCurveDock::dataChanged() { this->enableRecalculate(); }