File indexing completed on 2022-11-29 12:50:41

0001 /*
0002     SPDX-FileCopyrightText: 2009 Kashyap R Puranik <kashthealien@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "nuclearCalculator.h"
0008 
0009 #include <cmath>
0010 
0011 #include <KLocalizedString>
0012 
0013 #include "kalziumutils.h"
0014 #include "prefs.h"
0015 
0016 using namespace KUnitConversion;
0017 
0018 nuclearCalculator::nuclearCalculator(QWidget *parent)
0019     : QFrame(parent)
0020 {
0021     ui.setupUi(this);
0022 
0023     /**************************************************************************/
0024     //                       Nuclear Calculator set up                        //
0025     /**************************************************************************/
0026     KalziumDataObject *kdo = KalziumDataObject::instance();
0027 
0028     // add all element names to the comboBox in the user interface
0029     foreach (Element *e, kdo->ElementList) {
0030         ui.element->addItem(e->dataAsString(ChemicalDataObject::name));
0031     }
0032     /// FIXME
0033     /* The last three elemenents will be removed because information is not available
0034        and causes the program to crash when selected. */
0035     int count = ui.element->count();
0036     ui.element->removeItem(count - 1);
0037     ui.element->removeItem(count - 2);
0038     ui.element->removeItem(count - 3);
0039 
0040     // initialise data
0041     init();
0042     // Connect signals with slots
0043     connect(ui.element, SIGNAL(activated(int)), this, SLOT(elementChanged(int)));
0044     connect(ui.isotope, SIGNAL(activated(int)), this, SLOT(isotopeChanged(int)));
0045     connect(ui.halfLife, SIGNAL(valueChanged(double)), this, SLOT(halfLifeChanged()));
0046     connect(ui.halfLife_unit, SIGNAL(activated(int)), this, SLOT(halfLifeChanged()));
0047     connect(ui.initAmt, SIGNAL(valueChanged(double)), this, SLOT(initAmtChanged()));
0048     connect(ui.initAmt_unit, SIGNAL(activated(int)), this, SLOT(initAmtChanged()));
0049     connect(ui.initAmtType, SIGNAL(activated(int)), this, SLOT(initAmtChanged()));
0050     connect(ui.finalAmt, SIGNAL(valueChanged(double)), this, SLOT(finalAmtChanged()));
0051     connect(ui.finalAmt_unit, SIGNAL(activated(int)), this, SLOT(finalAmtChanged()));
0052     connect(ui.finalAmtType, SIGNAL(activated(int)), this, SLOT(finalAmtChanged()));
0053     connect(ui.time, SIGNAL(valueChanged(double)), this, SLOT(timeChanged()));
0054     connect(ui.time_unit, SIGNAL(activated(int)), this, SLOT(timeChanged()));
0055     connect(ui.slider, &QAbstractSlider::valueChanged, this, &nuclearCalculator::sliderMoved);
0056     connect(ui.mode, SIGNAL(activated(int)), this, SLOT(setMode(int)));
0057     connect(ui.reset, &QAbstractButton::clicked, this, &nuclearCalculator::init);
0058 
0059     /**************************************************************************/
0060     // Nuclear Calculator setup complete
0061     /**************************************************************************/
0062 
0063     if (Prefs::mass()) {
0064         ui.initAmtType->hide();
0065         ui.finalAmtType->hide();
0066     }
0067 }
0068 
0069 nuclearCalculator::~nuclearCalculator() = default;
0070 
0071 // The function that initialises data
0072 void nuclearCalculator::init()
0073 {
0074     const int ISOTOPE_NUM = 22;
0075     // Add all isotope names of Uranium (by default)to the isotope comboBox
0076     const QList<Isotope *> list = KalziumDataObject::instance()->isotopes(92);
0077     QString isotope;
0078 
0079     ui.isotope->clear();
0080     for (Isotope *i : list) {
0081         isotope.setNum(i->mass());
0082         ui.isotope->addItem(isotope);
0083     }
0084 
0085     // initialise the data, initially selected values (Uranium, 92, 238)
0086     ui.element->setCurrentIndex(91);
0087     ui.isotope->setCurrentIndex(ISOTOPE_NUM);
0088     ui.halfLife->setValue(list.at(ISOTOPE_NUM)->halflife());
0089     ui.initAmt->setValue(6.0);
0090     ui.finalAmt->setValue(3.0);
0091     ui.time->setValue(list.at(ISOTOPE_NUM)->halflife());
0092 
0093     timeUnitCombobox(ui.halfLife_unit);
0094 
0095     ui.initAmtType->setCurrentIndex(0);
0096 
0097     ui.finalAmtType->setCurrentIndex(0);
0098 
0099     massUnitCombobox(ui.initAmt_unit);
0100 
0101     massUnitCombobox(ui.finalAmt_unit);
0102 
0103     timeUnitCombobox(ui.time_unit);
0104 
0105     QString tempStr;
0106     tempStr.setNum(list.at(ISOTOPE_NUM)->mass());
0107     ui.mass->setText(tempStr);
0108 
0109     // Setup of the UI done
0110     // Initialise values
0111     m_initAmount = Value(6.0, KUnitConversion::Gram);
0112     m_finalAmount = Value(3.0, KUnitConversion::Gram);
0113     m_mass = list.at(ISOTOPE_NUM)->mass();
0114     m_time = Value(list.at(ISOTOPE_NUM)->halflife(), KUnitConversion::Year);
0115     m_halfLife = Value(list.at(ISOTOPE_NUM)->halflife(), KUnitConversion::Year);
0116 
0117     m_element = *KalziumDataObject::instance()->element(92);
0118     m_isotope = *list.at(ISOTOPE_NUM);
0119 
0120     setMode(2);
0121 }
0122 
0123 void nuclearCalculator::massUnitCombobox(QComboBox *comboBox)
0124 {
0125     QList<int> units;
0126     units << Gram << Milligram << Kilogram << Ton << Carat << Pound << Ounce << TroyOunce;
0127     KalziumUtils::populateUnitCombobox(comboBox, units);
0128 
0129     comboBox->setCurrentIndex(0);
0130 }
0131 
0132 void nuclearCalculator::timeUnitCombobox(QComboBox *comboBox)
0133 {
0134     QList<int> units;
0135     units << KUnitConversion::Year << KUnitConversion::Week << KUnitConversion::Day << KUnitConversion::Hour << KUnitConversion::Minute
0136           << KUnitConversion::Second;
0137 
0138     KalziumUtils::populateUnitCombobox(comboBox, units);
0139 
0140     comboBox->setCurrentIndex(0);
0141 }
0142 
0143 // This function is executed when the element is changed
0144 void nuclearCalculator::elementChanged(int index)
0145 {
0146     // set the newly chosen element
0147     m_element = *KalziumDataObject::instance()->element(index + 1);
0148 
0149     // Add all isotope names of Uranium (by default) to the isotope comboBox
0150     const QList<Isotope *> list = KalziumDataObject::instance()->isotopes(index + 1);
0151     QString isotope; // A temporary string
0152     ui.isotope->clear(); // Clear the contents of the combo box
0153 
0154     // update the combobox with isotopes of the new element
0155     for (Isotope *i : list) {
0156         isotope.setNum(i->mass());
0157         ui.isotope->addItem(isotope);
0158     }
0159 
0160     // Set the halfLife to that of the first isotope of the element.
0161     ui.halfLife->setValue(list.at(0)->halflife());
0162     // Recalculate and update
0163     calculate();
0164 }
0165 
0166 // This function is executed when the isotope is changed
0167 void nuclearCalculator::isotopeChanged(int index)
0168 {
0169     // update the nuclear Calculator
0170     int elementNumber = ui.element->currentIndex() + 1;
0171     QList<Isotope *> list = KalziumDataObject::instance()->isotopes(elementNumber);
0172     m_isotope = *list.at(index);
0173 
0174     // get the halfLife of the new isotope
0175     double halfLife = list.at(index)->halflife();
0176     m_mass = list.at(index)->mass();
0177 
0178     // A string in isotope for searching the right unit
0179     int halfLifeUnit = (list.at(index)->halflifeUnit().operator==("y")) ? KUnitConversion::Year : KUnitConversion::Second;
0180 
0181     QString tempStr;
0182     tempStr.setNum(m_mass);
0183     ui.mass->setText(tempStr);
0184     // Update the UI with the halfLife value
0185     ui.halfLife->setValue(halfLife);
0186     int x = ui.halfLife_unit->findData(halfLifeUnit);
0187     if (x >= 0) {
0188         ui.halfLife_unit->setCurrentIndex(x);
0189     }
0190     m_halfLife = Value(halfLife, KUnitConversion::UnitId(halfLifeUnit));
0191     // Recalculate and update
0192     calculate();
0193 }
0194 
0195 // This function is executed when the halfLife is changed
0196 void nuclearCalculator::halfLifeChanged()
0197 {
0198     // update the halfLife value
0199     m_halfLife = Value(ui.halfLife->value(), getUnitIdFromCombobox(ui.halfLife_unit));
0200     // recalculate the required
0201     calculate();
0202 }
0203 
0204 KUnitConversion::UnitId nuclearCalculator::getUnitIdFromCombobox(QComboBox *comboBox)
0205 {
0206     return KUnitConversion::UnitId(comboBox->itemData(comboBox->currentIndex()).toInt());
0207 }
0208 
0209 void nuclearCalculator::initAmtChanged()
0210 {
0211     // If quantity is specified in terms of mass, quantity <- (mass, unit)
0212     if (ui.initAmtType->currentIndex() == 0) {
0213         ui.initAmt_unit->show();
0214         m_initAmount = Value(ui.initAmt->value(), getUnitIdFromCombobox(ui.initAmt_unit));
0215     } else { // If quantity is specified in terms of moles quantity <- (moles * atomicMass, unit)
0216         ui.initAmt_unit->hide();
0217         m_initAmount = Value(((ui.initAmt->value()) * m_mass), getUnitIdFromCombobox(ui.initAmt_unit));
0218     }
0219 
0220     calculate();
0221 }
0222 
0223 void nuclearCalculator::finalAmtChanged()
0224 {
0225     // If quantity is specified in terms of mass, quantity <- (mass, unit)
0226     if (ui.finalAmtType->currentIndex() == 0) {
0227         ui.finalAmt_unit->show();
0228         m_finalAmount = Value(ui.finalAmt->value(), getUnitIdFromCombobox(ui.finalAmt_unit));
0229     } else { // If quantity is specified in terms of moles quantity <- (moles * atomicMass, unit)
0230         ui.finalAmt_unit->hide();
0231         m_finalAmount = Value(((ui.finalAmt->value()) * m_mass), getUnitIdFromCombobox(ui.finalAmt_unit));
0232     }
0233 
0234     calculate();
0235 }
0236 
0237 void nuclearCalculator::sliderMoved(int numHlives)
0238 {
0239     double num = numHlives / 10.0;
0240     m_time = Value(num * m_halfLife.number(), m_halfLife.unit());
0241 
0242     ui.time->setValue(m_time.number());
0243     ui.time_unit->setCurrentIndex(ui.halfLife_unit->currentIndex());
0244     ui.numHalfLives->setText(m_time.toString());
0245 }
0246 
0247 void nuclearCalculator::timeChanged()
0248 {
0249     m_time = Value(ui.time->value(), getUnitIdFromCombobox(ui.time_unit));
0250 
0251     calculate();
0252 }
0253 
0254 void nuclearCalculator::setMode(int mode)
0255 {
0256     m_mode = mode;
0257 
0258     ui.initAmt->setReadOnly(false);
0259     ui.finalAmt->setReadOnly(false);
0260     ui.time->setReadOnly(false);
0261 
0262     // set the quantity that should be calculated to readOnly
0263     switch (mode) {
0264     case INIT_AMT:
0265         ui.initAmt->setReadOnly(true);
0266         ui.time_in_halfLives->show();
0267         break;
0268     case FINAL_AMT:
0269         ui.finalAmt->setReadOnly(true);
0270         ui.time_in_halfLives->show();
0271         break;
0272     case TIME:
0273         ui.time->setReadOnly(true);
0274         ui.time_in_halfLives->hide();
0275         break;
0276     }
0277 
0278     calculate();
0279 }
0280 void nuclearCalculator::calculate()
0281 {
0282     error(RESET_NUKE_MESSAGE);
0283     // Validate the values involved in calculation
0284     if (m_halfLife.number() == 0.0) {
0285         error(HALFLIFE_ZERO);
0286         return;
0287     }
0288 
0289     switch (m_mode) {
0290     case 0: // calculate initial amount before given time
0291         if (ui.finalAmt->value() == 0.0) {
0292             error(FINAL_AMT_ZERO);
0293             return;
0294         }
0295         calculateInitAmount();
0296         break;
0297     case 1: // calculate final amount after given time
0298         if (ui.initAmt->value() == 0.0) {
0299             error(INIT_AMT_ZERO);
0300             return;
0301         }
0302         calculateFinalAmount();
0303         break;
0304     case 2: // final amount greater than initial
0305         if (m_finalAmount.number() > m_initAmount.convertTo(m_finalAmount.unit()).number()) {
0306             error(FINAL_AMT_GREATER);
0307             return;
0308         }
0309         // one of the amounts is 0.0
0310         if (ui.finalAmt->value() == 0.0) {
0311             error(FINAL_AMT_ZERO);
0312             return;
0313         } else if (ui.initAmt->value() == 0.0) {
0314             error(INIT_AMT_ZERO);
0315             return;
0316         }
0317         calculateTime();
0318         break;
0319     }
0320 }
0321 
0322 void nuclearCalculator::calculateInitAmount()
0323 {
0324     error(RESET_NUKE_MESSAGE);
0325 
0326     // If no time has elapsed, initial and final amounts are the same
0327     if (m_time.number() == 0.0) {
0328         m_initAmount = m_finalAmount.convertTo(m_initAmount.unit());
0329         ui.initAmt->setValue(m_initAmount.number());
0330         return;
0331     }
0332     // Calculate the number of halfLives that have elapsed
0333     double ratio = m_time.convertTo(m_halfLife.unit()).number() / m_halfLife.number();
0334     // find out the initial amount
0335     m_initAmount = Value(m_initAmount.number() * pow(2.0, ratio), m_initAmount.unit());
0336     // Convert into the required units
0337     m_initAmount = m_initAmount.convertTo(getUnitIdFromCombobox(ui.initAmt_unit));
0338     ui.initAmt->setValue(m_initAmount.number());
0339 }
0340 
0341 void nuclearCalculator::calculateFinalAmount()
0342 {
0343     // If no time has elapsed, initial and final amounts are the same
0344     if (m_time.number() == 0.0) {
0345         m_finalAmount = m_initAmount.convertTo(m_finalAmount.unit());
0346         ui.finalAmt->setValue(m_finalAmount.number());
0347         return;
0348     }
0349     // Calculate the number of halfLives that have elapsed
0350     double ratio = m_time.convertTo(m_halfLife.unit()).number() / m_halfLife.number();
0351     // Calculate the final amount
0352     m_finalAmount = Value(m_finalAmount.number() / pow(2.0, ratio), m_initAmount.unit());
0353     // Convert into the required units
0354     m_finalAmount = m_finalAmount.convertTo(getUnitIdFromCombobox(ui.finalAmt_unit));
0355     ui.finalAmt->setValue(m_finalAmount.number());
0356 }
0357 
0358 void nuclearCalculator::calculateTime()
0359 {
0360     // If initial and final masses are the same (both units and value)
0361     // the time is also 0
0362     if (m_initAmount.number() == m_finalAmount.number() && m_initAmount.unit() == m_finalAmount.unit()) {
0363         m_time = Value(0.0, m_time.unit());
0364         ui.time->setValue(0.0);
0365         return;
0366     }
0367 
0368     // calculate the ratio of final to initial masses
0369     double ratio = m_initAmount.convertTo(m_finalAmount.unit()).number() / m_finalAmount.number();
0370 
0371     // The number of halfLives (log 2 (x) = log x / log 2)
0372     double numHalfLives = log(ratio) / log(2.0);
0373     double time_value = numHalfLives * m_halfLife.number();
0374     // Calculate the total time taken
0375     Value time = Value(time_value, m_halfLife.unit());
0376     m_time = time.convertTo(getUnitIdFromCombobox(ui.time_unit));
0377     ui.time->setValue(m_time.number());
0378 
0379     return;
0380 }
0381 
0382 void nuclearCalculator::error(int mode)
0383 {
0384     switch (mode) { // Depending on the mode, set the error messages.
0385     case RESET_NUKE_MESSAGE:
0386         ui.error->setText(QLatin1String(""));
0387         break;
0388     case INIT_AMT_ZERO:
0389         ui.error->setText(i18n("Initial amount cannot be zero."));
0390         break;
0391     case FINAL_AMT_ZERO:
0392         ui.error->setText(i18n("Final amount cannot be zero."));
0393         break;
0394     case HALFLIFE_ZERO:
0395         ui.error->setText(i18n("Time is zero, please enter a valid value."));
0396         break;
0397     case FINAL_AMT_GREATER:
0398         ui.error->setText(i18n("The final amount is greater than the initial amount."));
0399         break;
0400     }
0401 }