pid.rs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use darkfi_sdk::{blockchain::Slot, pasta::pallas};
  19. use lazy_static::lazy_static;
  20. use super::float_10::{
  21. fbig2base, Float10, FLOAT10_NEG_ONE, FLOAT10_NEG_TWO, FLOAT10_ONE, FLOAT10_TWO, FLOAT10_ZERO,
  22. };
  23. /// PID controller configuration
  24. const P: &str = "28948022309329048855892746252171976963363056481941560715954676764349967630337";
  25. lazy_static! {
  26. static ref FIELD_P: Float10 = Float10::try_from(P).unwrap();
  27. static ref KP: Float10 = Float10::try_from("0.18").unwrap();
  28. static ref KI: Float10 = Float10::try_from("0.02").unwrap();
  29. static ref KD: Float10 = Float10::try_from("-0.1").unwrap();
  30. static ref MAX_F: Float10 = Float10::try_from("0.99").unwrap();
  31. static ref MIN_F: Float10 = Float10::try_from("0.01").unwrap();
  32. static ref EPSILON: Float10 = Float10::try_from("1").unwrap();
  33. }
  34. /// Return 2-term target approximation sigma coefficients,
  35. /// alogn with the inverse probability `f` of becoming a
  36. /// block producer and the feedback error, corresponding
  37. /// to provided slot consensus state,
  38. pub fn slot_pid_output(
  39. previous_slot: &Slot,
  40. previous_producers: u64,
  41. ) -> (f64, f64, pallas::Base, pallas::Base) {
  42. let (f, error) = calculate_f(previous_slot, previous_producers);
  43. let total_tokens =
  44. Float10::try_from(previous_slot.total_tokens + previous_slot.reward).unwrap();
  45. let (sigma1, sigma2) = calculate_sigmas(f.clone(), total_tokens);
  46. // TODO: log values
  47. (f.to_f64(), error.to_f64(), sigma1, sigma2)
  48. }
  49. /// Calculate the inverse probability `f` of becoming a block producer (winning the lottery)
  50. /// having all the tokens, and the feedback error, represented as Float10.
  51. fn calculate_f(previous_slot: &Slot, previous_producers: u64) -> (Float10, Float10) {
  52. // PID controller K values based on constants
  53. let k1 = KP.clone() + KI.clone() + KD.clone();
  54. let k2 = FLOAT10_NEG_ONE.clone() * KP.clone() + FLOAT10_NEG_TWO.clone() * KD.clone();
  55. let k3 = KD.clone();
  56. // Convert slot values to Float10
  57. let previous_slot_f = Float10::try_from(previous_slot.pid.f).unwrap();
  58. let previous_slot_error = Float10::try_from(previous_slot.pid.error).unwrap();
  59. let previous_slot_previous_slot_error =
  60. Float10::try_from(previous_slot.previous.error).unwrap();
  61. // Calculate feedback error based on previous block producers.
  62. let feedback = Float10::try_from(previous_producers).unwrap();
  63. let error = FLOAT10_ONE.clone() - feedback;
  64. // Calculate f
  65. let mut f = previous_slot_f +
  66. k1 * error.clone() +
  67. k2 * previous_slot_error +
  68. k3 * previous_slot_previous_slot_error;
  69. // Boundaries control
  70. if f <= *FLOAT10_ZERO {
  71. f = MIN_F.clone()
  72. } else if f >= *FLOAT10_ONE {
  73. f = MAX_F.clone()
  74. }
  75. (f, error)
  76. }
  77. /// Return 2-term target approximation sigma coefficients,
  78. /// corresponding to provided `f` and `total_tokens` values.
  79. fn calculate_sigmas(f: Float10, total_tokens: Float10) -> (pallas::Base, pallas::Base) {
  80. // Calculate `neg_c` value
  81. let x = FLOAT10_ONE.clone() - f;
  82. let c = x.ln();
  83. let neg_c = FLOAT10_NEG_ONE.clone() * c;
  84. // Calculate sigma 1
  85. let sigma1_fbig = neg_c.clone() / (total_tokens.clone() + EPSILON.clone()) * FIELD_P.clone();
  86. let sigma1 = fbig2base(sigma1_fbig);
  87. // Calculate sigma 2
  88. let sigma2_fbig = (neg_c / (total_tokens + EPSILON.clone())).powf(FLOAT10_TWO.clone()) *
  89. (FIELD_P.clone() / FLOAT10_TWO.clone());
  90. let sigma2 = fbig2base(sigma2_fbig);
  91. (sigma1, sigma2)
  92. }