File indexing completed on 2024-04-28 07:28:55
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 } 0402 0403 #include "moc_nuclearCalculator.cpp"