src/Utils.cpp

changeset 373
b02aca4e926c
parent 356
f60f5a1fece9
child 375
c21567bfd703
equal deleted inserted replaced
372:d9c78eb19728 373:b02aca4e926c
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(int Form, double SG, double Volume, double Amount, double T1, double T2, double Alpha) 394 double Utils::TinsethIBU(int Form, double SG, double Volume, double Amount, double T1, double T2, double Alpha, double Utilisation, double BU_factor)
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 double ibu = 0.0;
399
400 /*
401 * Most suppliers of CO2 hop extract (not isomerized) use formulas
402 * like this. For now, manual set a utilisation factor of 35%.
403 */
404 if (Form == HOP_FORMS_CO2EXTRACT) {
405 double utilisation = 0.35; // 35%
406 double factor_sg = 1.0;
407 double factor_bt = 1.0;
408 double bt = T2 - T1;
409
410 /* Table from Wildabouthops */
411 if (SG >= 1.150)
412 factor_sg = 1.3;
413 else if (SG >= 1.110)
414 factor_sg = 1.2;
415 else if (SG >= 1.080)
416 factor_sg = 1.1;
417
418 if (bt > 60) {
419 factor_bt = ((bt - 60) / 300) + 1.0;
420 } else {
421 factor_bt = (bt / 60.0);
422 }
423 qDebug() << "factor_sg" << factor_sg << "factor_bt" << factor_bt;
424
425 ibu = ((Utilisation / 100.0) * alpha * mass * 1000.0) / Volume;
426 ibu = (ibu * factor_bt) / factor_sg;
427 qDebug() << "TinsethIBU CO2" << Amount << Alpha << Volume << Utilisation << BU_factor << "SG" << SG << "T" << T1 << T2 << "ibu:" << ibu;
428 return ibu;
429 }
430
431 if (Form == HOP_FORMS_ISOEXTRACT) {
432
433 double dosageHl = ((100.0 / Utilisation) * (100.0 / Alpha) * 0.001) / BU_factor; // IBU per liter
434 qDebug() << (100 / Utilisation) << (100 / Alpha) << dosageHl << dosageHl * Volume << mass / (dosageHl * Volume);
435 ibu = mass / (dosageHl * Volume);
436 qDebug() << "TinsethIBU ISO" << Amount << Alpha << Volume << Utilisation << BU_factor << "ibu:" << ibu;
437 return ibu;
438 }
398 439
399 /* 440 /*
400 * Basic Tinseth formula. 441 * Basic Tinseth formula.
401 * http://realbeer.com/hops/research.html 442 * http://realbeer.com/hops/research.html
402 */ 443 */
403 double AddedAlphaAcids = (alpha * mass * 1000) / Volume; 444 double AddedAlphaAcids = (alpha * mass * 1000) / Volume;
404 double Bigness_factor = 1.65 * pow(0.000125, SG - 1); 445 double Bigness_factor = 1.65 * pow(0.000125, SG - 1);
405 double BoilTime_factor1 = ((1 - exp(-0.04 * T1)) / 4.15); 446 double BoilTime_factor1 = ((1 - exp(-0.04 * T1)) / 4.15);
406 double BoilTime_factor2 = ((1 - exp(-0.04 * T2)) / 4.15); 447 double BoilTime_factor2 = ((1 - exp(-0.04 * T2)) / 4.15);
407 double ibu = Bigness_factor * (BoilTime_factor2 - BoilTime_factor1) * AddedAlphaAcids; 448 ibu = Bigness_factor * (BoilTime_factor2 - BoilTime_factor1) * AddedAlphaAcids;
449 qDebug() << "TinsethIBU nor" << SG << Amount << Alpha << Volume << Bigness_factor * (BoilTime_factor2 - BoilTime_factor1) << "ibu:" << ibu;
408 450
409 /* 451 /*
410 * Correction for hop forms 452 * Correction for hop forms
411 */ 453 */
412 if (Form == HOP_FORMS_PELLET) { 454 if (Form == HOP_FORMS_PELLET) {
415 ibu *= (1 + my_factor_plug / 100.0); 457 ibu *= (1 + my_factor_plug / 100.0);
416 } else if (Form == HOP_FORMS_LEAF_WET) { 458 } 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 459 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) { 460 } else if (Form == HOP_FORMS_CRYO) {
419 ibu *= (1 + my_factor_cryohop / 100.0); 461 ibu *= (1 + my_factor_cryohop / 100.0);
420 } else if (Form == HOP_FORMS_EXTRACT) { 462 }
421 // Nothing for now. 463
422 } 464 qDebug() << "boilIBU" << Form << SG << Volume << Amount << BoilTime_factor2 << BoilTime_factor1 << Alpha << "IBU:" << ibu;
423
424 //qDebug() << "boilIBU" << Form << SG << Volume << Amount << T1 << T2 << Alpha << "IBU:" << ibu;
425 return ibu; 465 return ibu;
426 } 466 }
427 467
428 468
429 double Utils::toIBU(int Use, int Form, double SG, double Volume, double Amount, double Boiltime, double Alpha, 469 double Utils::toIBU(int Use, int Form, double preSG, double postSG, double Volume, double Amount, double Boiltime, double Alpha,
430 int Method, double Whirlpool9, double Whirlpool7, double Whirlpool6, double Fulltime, int Cooltype, double Coolparm1, double Coolparm2) 470 int Method, double Whirlpool9, double Whirlpool7, double Whirlpool6, double Fulltime,
471 int Cooltype, double Coolparm1, double Coolparm2, double Utilisation, double BU_factor)
431 { 472 {
432 double ibu = 0.0; 473 double ibu = 0.0;
433 474
434 if (Use == HOP_USEAT_MASH) { 475 if (Use == HOP_USEAT_MASH) {
435 /* 476 /*
438 * Almost all these hops will be gone after removing the malt. From 479 * Almost all these hops will be gone after removing the malt. From
439 * pellets there may be some dust left, but that has minor effects. 480 * pellets there may be some dust left, but that has minor effects.
440 * 481 *
441 * http://scottjanish.com/the-locksmith-utilizing-bioengineered-yeast-and-high-bound-thiol-precersour-hops-and-phantasm-powder-to-thiol-drive-beer/ 482 * http://scottjanish.com/the-locksmith-utilizing-bioengineered-yeast-and-high-bound-thiol-precersour-hops-and-phantasm-powder-to-thiol-drive-beer/
442 */ 483 */
443 ibu = TinsethIBU(Form, SG, Volume, Amount, 0, 60, Alpha) * (1 + my_factor_mashhop / 100.0); 484 ibu = TinsethIBU(Form, preSG, Volume, Amount, 0, 60, Alpha, Utilisation, BU_factor) * (1 + my_factor_mashhop / 100.0);
444 485
445 } else if ((Use == HOP_USEAT_FWH) || (Use == HOP_USEAT_BOIL)) { 486 } else if ((Use == HOP_USEAT_FWH) || (Use == HOP_USEAT_BOIL)) {
446 /* 487 /*
447 * IBU's from hops during FWH and boil. 488 * IBU's from hops during FWH and boil.
448 */ 489 */
449 double boil_time = Fulltime; 490 double boil_time = Fulltime;
450 if (Use == HOP_USEAT_BOIL) 491 if (Use == HOP_USEAT_BOIL)
451 boil_time = Boiltime; 492 boil_time = Boiltime;
452 ibu = TinsethIBU(Form, SG, Volume, Amount, 0, boil_time, Alpha); 493 double fromSG = postSG - ((boil_time / Fulltime) * (postSG - preSG)); /* SG when this hop addition starts */
494 double avgSG = (postSG + fromSG) / 2; /* Average SG during this addition */
495 ibu = TinsethIBU(Form, avgSG, Volume, Amount, 0, boil_time, Alpha, Utilisation, BU_factor);
453 496
454 /* 497 /*
455 * Corrections for Mash and FWH 498 * Correction for FWH
456 */ 499 */
457 if (Use == HOP_USEAT_FWH) { 500 if (Use == HOP_USEAT_FWH) {
458 ibu *= (1 + my_factor_fwh / 100.0); 501 ibu *= (1 + my_factor_fwh / 100.0);
459 } 502 }
460 503
464 507
465 /* 508 /*
466 * Flameout, currently fixed 1 minute. 509 * Flameout, currently fixed 1 minute.
467 */ 510 */
468 double flameout_time = 1; 511 double flameout_time = 1;
469 double fibu = TinsethIBU(Form, SG, Volume, Amount, boil_time, boil_time + flameout_time, Alpha); 512 double fibu = TinsethIBU(Form, postSG, Volume, Amount, boil_time, boil_time + flameout_time, Alpha, Utilisation, BU_factor);
470 fibu *= IBU_reduction(98.0); 513 fibu *= IBU_reduction(98.0);
471 //qDebug() << "during flameout" << fibu; 514 //qDebug() << "during flameout" << fibu;
472 nibu += fibu; 515 nibu += fibu;
473 516
474 // Add this hop during cooling 517 // Add this hop during cooling
475 /* 518 /*
476 * Hopstands, this boil hop adds some IBU's too. 519 * Hopstands, this boil hop adds some IBU's too.
477 */ 520 */
478 if (Whirlpool9) { 521 if (Whirlpool9) {
479 double wibu9 = TinsethIBU(Form, SG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool9, Alpha); 522 double wibu9 = TinsethIBU(Form, postSG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool9, Alpha, Utilisation, BU_factor);
480 wibu9 *= IBU_reduction(87.0); 523 wibu9 *= IBU_reduction(87.0);
481 //qDebug() << "during whirlpool9" << wibu9; 524 //qDebug() << "during whirlpool9" << wibu9;
482 nibu += wibu9; 525 nibu += wibu9;
483 } 526 }
484 if (Whirlpool7) { 527 if (Whirlpool7) {
485 double wibu7 = TinsethIBU(Form, SG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool7, Alpha); 528 double wibu7 = TinsethIBU(Form, postSG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool7, Alpha, Utilisation, BU_factor);
486 wibu7 *= IBU_reduction(74.0); 529 wibu7 *= IBU_reduction(74.0);
487 //qDebug() << "during whirlpool7" << wibu7; 530 //qDebug() << "during whirlpool7" << wibu7;
488 nibu += wibu7; 531 nibu += wibu7;
489 } 532 }
490 if (Whirlpool6) { 533 if (Whirlpool6) {
491 double wibu6 = TinsethIBU(Form, SG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool6, Alpha); 534 double wibu6 = TinsethIBU(Form, postSG, Volume, Amount, boil_time + flameout_time, boil_time + flameout_time + Whirlpool6, Alpha, Utilisation, BU_factor);
492 wibu6 *= IBU_reduction(63.0); 535 wibu6 *= IBU_reduction(63.0);
493 //qDebug() << "during whirlpool6" << wibu6; 536 //qDebug() << "during whirlpool6" << wibu6;
494 nibu += wibu6; 537 nibu += wibu6;
495 } 538 }
496 //qDebug() << "Old IBU" << ibu << "New IBU" << nibu; 539 //qDebug() << "Old IBU" << ibu << "New IBU" << nibu;
500 } else if ((Use == HOP_USEAT_AROMA) && (Method > 0)) { 543 } else if ((Use == HOP_USEAT_AROMA) && (Method > 0)) {
501 /* 544 /*
502 * At flameout, and only using extended calculation. 545 * At flameout, and only using extended calculation.
503 */ 546 */
504 double flameout_time = 1; 547 double flameout_time = 1;
505 ibu = TinsethIBU(Form, SG, Volume, Amount, 0, flameout_time, Alpha); 548 ibu = TinsethIBU(Form, postSG, Volume, Amount, 0, flameout_time, Alpha, Utilisation, BU_factor);
506 ibu *= IBU_reduction(98.0); 549 ibu *= IBU_reduction(98.0);
507 /* 550 /*
508 * Hopstands, this flameout hop adds some IBU's too. 551 * Hopstands, this flameout hop adds some IBU's too.
509 */ 552 */
510 if (Whirlpool9) { 553 if (Whirlpool9) {
511 double wibu9 = TinsethIBU(Form, SG, Volume, Amount, flameout_time, flameout_time + Whirlpool9, Alpha); 554 double wibu9 = TinsethIBU(Form, postSG, Volume, Amount, flameout_time, flameout_time + Whirlpool9, Alpha, Utilisation, BU_factor);
512 wibu9 *= IBU_reduction(87.0); 555 wibu9 *= IBU_reduction(87.0);
513 //qDebug() << "during whirlpool9" << wibu9; 556 //qDebug() << "during whirlpool9" << wibu9;
514 ibu += wibu9; 557 ibu += wibu9;
515 } 558 }
516 if (Whirlpool7) { 559 if (Whirlpool7) {
517 double wibu7 = TinsethIBU(Form, SG, Volume, Amount, flameout_time, flameout_time + Whirlpool7, Alpha); 560 double wibu7 = TinsethIBU(Form, postSG, Volume, Amount, flameout_time, flameout_time + Whirlpool7, Alpha, Utilisation, BU_factor);
518 wibu7 *= IBU_reduction(74.0); 561 wibu7 *= IBU_reduction(74.0);
519 //qDebug() << "during whirlpool7" << wibu7; 562 //qDebug() << "during whirlpool7" << wibu7;
520 ibu += wibu7; 563 ibu += wibu7;
521 } 564 }
522 if (Whirlpool6) { 565 if (Whirlpool6) {
523 double wibu6 = TinsethIBU(Form, SG, Volume, Amount, flameout_time, flameout_time + Whirlpool6, Alpha); 566 double wibu6 = TinsethIBU(Form, postSG, Volume, Amount, flameout_time, flameout_time + Whirlpool6, Alpha, Utilisation, BU_factor);
524 wibu6 *= IBU_reduction(63.0); 567 wibu6 *= IBU_reduction(63.0);
525 //qDebug() << "during whirlpool6" << wibu6; 568 //qDebug() << "during whirlpool6" << wibu6;
526 ibu += wibu6; 569 ibu += wibu6;
527 } 570 }
528 571
529 } else if ((Use == HOP_USEAT_WHIRLPOOL) && (Method > 0)) { 572 } else if ((Use == HOP_USEAT_WHIRLPOOL) && (Method > 0)) {
530 /* 573 /*
531 * Hopstands. 574 * Hopstands.
532 */ 575 */
533 if (Whirlpool9) { 576 if (Whirlpool9) {
534 double wibu9 = TinsethIBU(Form, SG, Volume, Amount, 0, Whirlpool9, Alpha); 577 double wibu9 = TinsethIBU(Form, postSG, Volume, Amount, 0, Whirlpool9, Alpha, Utilisation, BU_factor);
535 wibu9 *= IBU_reduction(87.0); 578 wibu9 *= IBU_reduction(87.0);
536 //qDebug() << "during whirlpool9" << wibu9; 579 //qDebug() << "during whirlpool9" << wibu9;
537 ibu = wibu9; 580 ibu = wibu9;
538 } 581 }
539 if (Whirlpool7) { 582 if (Whirlpool7) {
540 double wibu7 = TinsethIBU(Form, SG, Volume, Amount, 0, Whirlpool7, Alpha); 583 double wibu7 = TinsethIBU(Form, postSG, Volume, Amount, 0, Whirlpool7, Alpha, Utilisation, BU_factor);
541 wibu7 *= IBU_reduction(74.0); 584 wibu7 *= IBU_reduction(74.0);
542 //qDebug() << "during whirlpool7" << wibu7; 585 //qDebug() << "during whirlpool7" << wibu7;
543 ibu = wibu7; 586 ibu = wibu7;
544 } 587 }
545 if (Whirlpool6) { 588 if (Whirlpool6) {
546 double wibu6 = TinsethIBU(Form, SG, Volume, Amount, 0, Whirlpool6, Alpha); 589 double wibu6 = TinsethIBU(Form, postSG, Volume, Amount, 0, Whirlpool6, Alpha, Utilisation, BU_factor);
547 wibu6 *= IBU_reduction(63.0); 590 wibu6 *= IBU_reduction(63.0);
548 //qDebug() << "during whirlpool6" << wibu6; 591 //qDebug() << "during whirlpool6" << wibu6;
549 ibu = wibu6; 592 ibu = wibu6;
550 } 593 }
594 } else if (Use == HOP_USEAT_BOTTLING) {
595 /*
596 * Isomerized hop extracts to use at bottling.
597 * Assume 10% volume is lost during fermentation and transfers.
598 */
599 ibu = TinsethIBU(Form, postSG, Volume * 0.9, Amount, 0, 0, Alpha, Utilisation, BU_factor);
551 } 600 }
552 601
553 double rc = round(ibu * 1000.0) / 1000.0; 602 double rc = round(ibu * 1000.0) / 1000.0;
554 603
555 qDebug() << "toIBU" << Use << Form << SG << Volume << Amount << Boiltime << Alpha << Method << Whirlpool9 << Whirlpool7 << Whirlpool6 << Fulltime << Cooltype << Coolparm1 << Coolparm2 << "rc:" << rc; 604 qDebug() << "toIBU" << Use << Form << preSG << postSG << Volume << Amount << Boiltime << Alpha << Method << Whirlpool9 << Whirlpool7 << Whirlpool6 << Fulltime << Cooltype << Coolparm1 << Coolparm2 << Utilisation << BU_factor << "rc:" << rc;
556 return rc; 605 return rc;
557 } 606 }
558 607
559 608
560 double Utils::hopFlavourContribution(double bt, double vol, int use, double amount) 609 double Utils::hopFlavourContribution(double bt, double vol, int use, double amount, int form)
561 { 610 {
562 double result; 611 double result;
563 612
564 if (use == HOP_USEAT_WHIRLPOOL || use == HOP_USEAT_DRY_HOP) 613 if (use == HOP_USEAT_WHIRLPOOL || use == HOP_USEAT_DRY_HOP)
565 return 0; 614 return 0;
574 } 623 }
575 return (result * amount * 1000.0) / vol; 624 return (result * amount * 1000.0) / vol;
576 } 625 }
577 626
578 627
579 double Utils::hopAromaContribution(double bt, double vol, int use, double amount) 628 double Utils::hopAromaContribution(double bt, double vol, int use, double amount, int form)
580 { 629 {
581 double result = 0.0; 630 double result = 0.0;
631 double factor = 1.0;
632
633 if (form == HOP_FORMS_CRYO)
634 factor = 2.0;
582 635
583 if (use == HOP_USEAT_DRY_HOP) { 636 if (use == HOP_USEAT_DRY_HOP) {
584 result = 1.33; 637 result = 1.33;
585 } else if (use == HOP_USEAT_WHIRLPOOL) { 638 } else if (use == HOP_USEAT_WHIRLPOOL) {
586 if (bt > 30) 639 if (bt > 30)
593 } else if (use == HOP_USEAT_BOIL) { // Boil 646 } else if (use == HOP_USEAT_BOIL) { // Boil
594 result = 1; 647 result = 1;
595 } else if (use == HOP_USEAT_AROMA) { // Aroma 648 } else if (use == HOP_USEAT_AROMA) { // Aroma
596 result = 1.2; 649 result = 1.2;
597 } 650 }
598 return (result * amount * 1000.0) / vol; 651 return (result * amount * factor * 1000.0) / vol;
599 } 652 }
600 653
601 654
602 double Utils::mix(double v1, double v2, double c1, double c2) 655 double Utils::mix(double v1, double v2, double c1, double c2)
603 { 656 {

mercurial