389 */ |
389 */ |
390 return 2.39 * pow(10, 11) * exp(-(9773 / (Tc + Kelvin))) * 1 / 1.009231743647; |
390 return 2.39 * pow(10, 11) * exp(-(9773 / (Tc + Kelvin))) * 1 / 1.009231743647; |
391 } |
391 } |
392 |
392 |
393 |
393 |
394 double Utils::TinsethIBU(double SG, double Volume, double Amount, double Time, double Alpha) |
394 double Utils::TinsethIBU(int Form, double SG, double Volume, double Amount, double T1, double T2, double Alpha) |
395 { |
395 { |
396 double alpha = Alpha / 100.0; |
396 double alpha = Alpha / 100.0; |
397 double mass = Amount * 1000.0; |
397 double mass = Amount * 1000.0; |
398 |
398 |
399 /* |
399 /* |
400 * Basic Tinseth formula. |
400 * Basic Tinseth formula. |
401 * http://realbeer.com/hops/research.html |
401 * http://realbeer.com/hops/research.html |
402 */ |
402 */ |
403 double AddedAlphaAcids = (alpha * mass * 1000) / Volume; |
403 double AddedAlphaAcids = (alpha * mass * 1000) / Volume; |
404 double Bigness_factor = 1.65 * pow(0.000125, SG - 1); |
404 double Bigness_factor = 1.65 * pow(0.000125, SG - 1); |
405 double BoilTime_factor = ((1 - exp(-0.04 * Time)) / 4.15); |
405 double BoilTime_factor1 = ((1 - exp(-0.04 * T1)) / 4.15); |
406 double ibu = Bigness_factor * BoilTime_factor * AddedAlphaAcids; |
406 double BoilTime_factor2 = ((1 - exp(-0.04 * T2)) / 4.15); |
407 |
407 double ibu = Bigness_factor * (BoilTime_factor2 - BoilTime_factor1) * AddedAlphaAcids; |
408 qDebug() << "boilIBU" << SG << Volume << Amount << Time << Alpha << "IBU:" << ibu; |
408 |
|
409 /* |
|
410 * Correction for hop forms |
|
411 */ |
|
412 if (Form == HOP_FORMS_PELLET) { |
|
413 ibu *= (1 + my_factor_pellet / 100.0); |
|
414 } else if (Form == HOP_FORMS_PLUG) { |
|
415 ibu *= (1 + my_factor_plug / 100.0); |
|
416 } else if (Form == HOP_FORMS_LEAF_WET) { |
|
417 ibu *= (1 + my_factor_wethop / 100.0); // From https://github.com/chrisgilmerproj/brewday/blob/master/brew/constants.py |
|
418 } else if (Form == HOP_FORMS_CRYO) { |
|
419 ibu *= (1 + my_factor_cryohop / 100.0); |
|
420 } else if (Form == HOP_FORMS_EXTRACT) { |
|
421 // Nothing for now. |
|
422 } |
|
423 |
|
424 qDebug() << "boilIBU" << Form << SG << Volume << Amount << T1 << T2 << Alpha << "IBU:" << ibu; |
409 return ibu; |
425 return ibu; |
410 } |
426 } |
411 |
427 |
412 |
428 |
413 double Utils::toIBU(int Use, int Form, double SG, double Volume, double Amount, double Boiltime, double Alpha, |
429 double Utils::toIBU(int Use, int Form, double SG, double Volume, double Amount, double Boiltime, double Alpha, |
414 int Method, double Whirlpool9, double Whirlpool7, double Whirlpool6, double Fulltime, int Cooltype, double Coolparm1, double Coolparm2) |
430 int Method, double Whirlpool9, double Whirlpool7, double Whirlpool6, double Fulltime, int Cooltype, double Coolparm1, double Coolparm2) |
415 { |
431 { |
416 double steep_time = 0; /* Total time a hop in the kettle. */ |
|
417 double loss_boiltemp = 1.0; /* Loss due to the lower boil temperature at higher altitude. */ |
432 double loss_boiltemp = 1.0; /* Loss due to the lower boil temperature at higher altitude. */ |
418 |
433 |
419 double ibu = 0.0, whirlibus = 0.0; |
434 double ibu = 0.0, whirlibus = 0.0; |
420 double alpha = Alpha / 100.0; |
435 double alpha = Alpha / 100.0; |
421 double mass = Amount * 1000.0; |
436 double mass = Amount * 1000.0; |
422 |
|
423 // Ideas from Zymurgy March-April 2018. These are not exact formulas! |
|
424 if (Use == HOP_USEAT_AROMA) { |
|
425 if (Whirlpool9) { // Flameout hops are 2 minutes in this range. |
|
426 whirlibus += (alpha * mass * 20) / Volume * (2.0 / 50.0); |
|
427 } |
|
428 if (Whirlpool7) { // Flameout hops are 4 minutes in this range. |
|
429 whirlibus += (alpha * mass * 6) / Volume * (4.0 / 50.0); |
|
430 } |
|
431 // Experiment. |
|
432 // double wibu = boilIBU(Form, SG, Volume, Amount, 6, Alpha, Method); // IBU's for 6 minutes full |
|
433 // double fibu = wibu * 0.067 * IBU_reduction(94); // Add timed segments |
|
434 // fibu += wibu * 0.1 * IBU_reduction(84); |
|
435 // fibu += wibu * 0.167 * IBU_reduction(74); |
|
436 // fibu += wibu * 0.250 * IBU_reduction(64); |
|
437 // fibu += wibu * 0.417 * IBU_reduction(54); |
|
438 //qDebug() << " 94" << wibu * 0.067 * IBU_reduction(94); |
|
439 //qDebug() << " 84" << wibu * 0.1 * IBU_reduction(84); |
|
440 //qDebug() << " 74" << wibu * 0.167 * IBU_reduction(74); |
|
441 //qDebug() << " 64" << wibu * 0.250 * IBU_reduction(64); |
|
442 //qDebug() << " 54" << wibu * 0.417 * IBU_reduction(54); |
|
443 // qDebug() << "flamout" << wibu << fibu; |
|
444 } |
|
445 if (Use == HOP_USEAT_WHIRLPOOL) { // Flameout or any whirlpool |
|
446 if (Whirlpool9) { |
|
447 // 20 mg/l/50 min |
|
448 whirlibus += (alpha * mass * 20) / Volume * (Whirlpool9 / 50.0); |
|
449 //qDebug() << "Whirlpool9" << alpha * mass * 20 << " liter:" << liters << " time:" << Whirlpool9 << " ibu" << (alpha * mass * 20) / liters * (Whirlpool9 / 50.0); |
|
450 } |
|
451 if (Whirlpool7) { |
|
452 // 6 mg/l/50 min |
|
453 whirlibus += (alpha * mass * 6) / Volume * (Whirlpool7 / 50.0); |
|
454 //qDebug() << "Whirlpool7" << alpha * mass * 6 << " liter:" << liters << " time:" << Whirlpool7 << " ibu" << (alpha * mass * 6) / liters * (Whirlpool7 / 50.0); |
|
455 } |
|
456 if (Whirlpool6) { |
|
457 // 2 mg/l/50 min |
|
458 whirlibus += (alpha * mass * 2) / Volume * (Whirlpool6 / 50.0); |
|
459 } |
|
460 // double wibu = boilIBU(Form, SG, Volume, Amount, Boiltime, Alpha, Method); |
|
461 // qDebug() << "whirpool" << wibu << wibu * IBU_reduction(74); |
|
462 } |
|
463 |
|
464 if ((Use == HOP_USEAT_MASH) || (Use == HOP_USEAT_FWH) || (Use == HOP_USEAT_BOIL)) { |
|
465 steep_time += Boiltime; |
|
466 } |
|
467 |
437 |
468 /* |
438 /* |
469 * IBU's from hops during Mash, FWH and boil. |
439 * IBU's from hops during Mash, FWH and boil. |
470 */ |
440 */ |
471 if ((Use == HOP_USEAT_MASH) || (Use == HOP_USEAT_FWH) || (Use == HOP_USEAT_BOIL)) { |
441 if ((Use == HOP_USEAT_MASH) || (Use == HOP_USEAT_FWH) || (Use == HOP_USEAT_BOIL)) { |
472 ibu = TinsethIBU(SG, Volume, Amount, Fulltime, Alpha); |
442 double boil_time = Fulltime; |
|
443 if (Use == HOP_USEAT_BOIL) |
|
444 boil_time = Boiltime; |
|
445 ibu = TinsethIBU(Form, SG, Volume, Amount, 0, boil_time, Alpha); |
|
446 |
473 /* |
447 /* |
474 * Corrections for Mash and FWH |
448 * Corrections for Mash and FWH |
475 */ |
449 */ |
476 if (Use == HOP_USEAT_MASH) { |
450 if (Use == HOP_USEAT_MASH) { |
477 ibu *= (1 + my_factor_mashhop / 100.0); |
451 ibu *= (1 + my_factor_mashhop / 100.0); |
478 } |
452 } |
479 if (Use == HOP_USEAT_FWH) { |
453 if (Use == HOP_USEAT_FWH) { |
480 ibu *= (1 + my_factor_fwh / 100.0); |
454 ibu *= (1 + my_factor_fwh / 100.0); |
481 } |
455 } |
482 |
456 |
|
457 if (Method > 0) { |
|
458 double nibu = ibu; |
|
459 loss_boiltemp = IBU_reduction(boilPoint()); |
|
460 nibu *= loss_boiltemp; |
|
461 qDebug() << "ibu" << nibu << "loss_boiltemp" << loss_boiltemp; |
|
462 |
|
463 /* |
|
464 * Flameout, currently fixed 1 minute. |
|
465 */ |
|
466 double flameout_time = 1; |
|
467 double fibu = TinsethIBU(Form, SG, Volume, Amount, boil_time, boil_time + flameout_time, Alpha); |
|
468 fibu *= IBU_reduction(98.0); |
|
469 qDebug() << "during flameout" << fibu; |
|
470 nibu += fibu; |
|
471 |
|
472 // Add this hop during cooling |
|
473 /* |
|
474 * Hopstands, this boil hop adds some IBU's too. |
|
475 */ |
|
476 if (Whirlpool9) { |
|
477 double wibu9 = TinsethIBU(Form, SG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool9, Alpha); |
|
478 wibu9 *= IBU_reduction(87.0); |
|
479 //qDebug() << "during whirlpool9" << wibu9; |
|
480 nibu += wibu9; |
|
481 } |
|
482 if (Whirlpool7) { |
|
483 double wibu7 = TinsethIBU(Form, SG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool7, Alpha); |
|
484 wibu7 *= IBU_reduction(74.0); |
|
485 //qDebug() << "during whirlpool7" << wibu7; |
|
486 nibu += wibu7; |
|
487 } |
|
488 if (Whirlpool6) { |
|
489 double wibu6 = TinsethIBU(Form, SG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool6, Alpha); |
|
490 wibu6 *= IBU_reduction(63.0); |
|
491 //qDebug() << "during whirlpool6" << wibu6; |
|
492 nibu += wibu6; |
|
493 } |
|
494 qDebug() << "Old IBU" << ibu << "New IBU" << nibu; |
|
495 ibu = nibu; |
|
496 } |
|
497 |
|
498 } else if ((Use == HOP_USEAT_AROMA) && (Method > 0)) { |
483 /* |
499 /* |
484 * Correction for hop forms |
500 * At flameout, and only using extended calculation. |
485 */ |
501 */ |
486 if (Form == HOP_FORMS_PELLET) { |
502 double flameout_time = 1; |
487 ibu *= (1 + my_factor_pellet / 100.0); |
503 ibu = TinsethIBU(Form, SG, Volume, Amount, 0, flameout_time, Alpha); |
488 } else if (Form == HOP_FORMS_PLUG) { |
504 ibu *= IBU_reduction(98.0); |
489 ibu *= (1 + my_factor_plug / 100.0); |
|
490 } else if (Form == HOP_FORMS_LEAF_WET) { |
|
491 ibu *= (1 + my_factor_wethop / 100.0); // From https://github.com/chrisgilmerproj/brewday/blob/master/brew/constants.py |
|
492 } else if (Form == HOP_FORMS_CRYO) { |
|
493 ibu *= (1 + my_factor_cryohop / 100.0); |
|
494 } else if (Form == HOP_FORMS_EXTRACT) { |
|
495 // Nothing for now. |
|
496 } |
|
497 |
|
498 if (Method > 0) { |
|
499 loss_boiltemp = IBU_reduction(boilPoint()); |
|
500 ibu *= loss_boiltemp; |
|
501 qDebug() << "ibu" << ibu << "loss_boiltemp" << loss_boiltemp; |
|
502 } |
|
503 |
|
504 // } else if (Use == HOP_USEAT_AROMA) { |
|
505 /* |
505 /* |
506 * At flameout. The cooling method is important. |
506 * Hopstands, this flameout hop adds some IBU's too. |
507 * Emersion chiller, Counterflow chiller, Au bain marie or natural. |
507 */ |
508 * Assume the hop is removed for all methods except Emersion chilling. |
508 if (Whirlpool9) { |
509 */ |
509 double wibu9 = TinsethIBU(Form, SG, Volume, Amount, flameout_time, flameout_time + Whirlpool9, Alpha); |
510 } else { |
510 wibu9 *= IBU_reduction(87.0); |
511 // qDebug() << "whirlibus" << whirlibus << Use; |
511 //qDebug() << "during whirlpool9" << wibu9; |
512 } |
512 ibu += wibu9; |
513 |
513 } |
514 return round((ibu + whirlibus) * 100.0) / 100.0; |
514 if (Whirlpool7) { |
|
515 double wibu7 = TinsethIBU(Form, SG, Volume, Amount, flameout_time, flameout_time + Whirlpool7, Alpha); |
|
516 wibu7 *= IBU_reduction(74.0); |
|
517 //qDebug() << "during whirlpool7" << wibu7; |
|
518 ibu += wibu7; |
|
519 } |
|
520 if (Whirlpool6) { |
|
521 double wibu6 = TinsethIBU(Form, SG, Volume, Amount, flameout_time, flameout_time + Whirlpool6, Alpha); |
|
522 wibu6 *= IBU_reduction(63.0); |
|
523 //qDebug() << "during whirlpool6" << wibu6; |
|
524 ibu += wibu6; |
|
525 } |
|
526 |
|
527 } else if ((Use == HOP_USEAT_WHIRLPOOL) && (Method > 0)) { |
|
528 /* |
|
529 * Hopstands. |
|
530 */ |
|
531 if (Whirlpool9) { |
|
532 double wibu9 = TinsethIBU(Form, SG, Volume, Amount, 0, Whirlpool9, Alpha); |
|
533 wibu9 *= IBU_reduction(87.0); |
|
534 //qDebug() << "during whirlpool9" << wibu9; |
|
535 ibu = wibu9; |
|
536 } |
|
537 if (Whirlpool7) { |
|
538 double wibu7 = TinsethIBU(Form, SG, Volume, Amount, 0, Whirlpool7, Alpha); |
|
539 wibu7 *= IBU_reduction(74.0); |
|
540 //qDebug() << "during whirlpool7" << wibu7; |
|
541 ibu = wibu7; |
|
542 } |
|
543 if (Whirlpool6) { |
|
544 double wibu6 = TinsethIBU(Form, SG, Volume, Amount, 0, Whirlpool6, Alpha); |
|
545 wibu6 *= IBU_reduction(63.0); |
|
546 //qDebug() << "during whirlpool6" << wibu6; |
|
547 ibu = wibu6; |
|
548 } |
|
549 } |
|
550 |
|
551 double rc = round((ibu + whirlibus) * 100.0) / 100.0; |
|
552 |
|
553 qDebug() << "toIBU" << Use << Form << SG << Volume << Amount << Boiltime << Alpha << Method << Whirlpool9 << Whirlpool7 << Whirlpool6 << Fulltime << Cooltype << Coolparm1 << Coolparm2 << "rc:" << rc; |
|
554 return rc; |
515 } |
555 } |
516 |
556 |
517 |
557 |
518 double Utils::hopFlavourContribution(double bt, double vol, int use, double amount) |
558 double Utils::hopFlavourContribution(double bt, double vol, int use, double amount) |
519 { |
559 { |