Wideband autonomous SDR analysis engine forked from sdr-visual-suite
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

366 lines
8.0KB

  1. package pipeline
  2. import "strings"
  3. type BudgetRebalance struct {
  4. Mode string `json:"mode,omitempty"`
  5. MaxShift int `json:"max_shift,omitempty"`
  6. Active bool `json:"active,omitempty"`
  7. Protect []string `json:"protect,omitempty"`
  8. Favor []string `json:"favor,omitempty"`
  9. Reasons []string `json:"reasons,omitempty"`
  10. Adjustments BudgetRebalanceAdjustments `json:"adjustments,omitempty"`
  11. favorWeights map[string]float64 `json:"-"`
  12. protectMap map[string]bool `json:"-"`
  13. }
  14. type BudgetRebalanceAdjustments struct {
  15. Refinement int `json:"refinement,omitempty"`
  16. Record int `json:"record,omitempty"`
  17. Decode int `json:"decode,omitempty"`
  18. }
  19. type rebalanceQueue struct {
  20. name string
  21. baseMax int
  22. max int
  23. pressure BudgetPressure
  24. protect bool
  25. favor float64
  26. }
  27. func ApplyBudgetRebalance(policy Policy, budget BudgetModel, pressure BudgetPressureSummary) BudgetModel {
  28. state := buildRebalanceState(policy)
  29. budget.Rebalance = state
  30. if state.MaxShift <= 0 {
  31. return budget
  32. }
  33. queues := []rebalanceQueue{
  34. {
  35. name: "refinement",
  36. baseMax: budget.Refinement.Max,
  37. max: budget.Refinement.Max,
  38. pressure: pressure.Refinement,
  39. protect: false,
  40. favor: state.favorWeight("refinement"),
  41. },
  42. {
  43. name: "record",
  44. baseMax: budget.Record.Max,
  45. max: budget.Record.Max,
  46. pressure: pressure.Record,
  47. protect: state.protects("record"),
  48. favor: state.favorWeight("record"),
  49. },
  50. {
  51. name: "decode",
  52. baseMax: budget.Decode.Max,
  53. max: budget.Decode.Max,
  54. pressure: pressure.Decode,
  55. protect: state.protects("decode"),
  56. favor: state.favorWeight("decode"),
  57. },
  58. }
  59. for i := 0; i < state.MaxShift; i++ {
  60. recvIdx := pickRebalanceReceiver(queues)
  61. donorIdx := pickRebalanceDonor(queues)
  62. if recvIdx < 0 || donorIdx < 0 || recvIdx == donorIdx {
  63. break
  64. }
  65. if queues[donorIdx].max <= 1 {
  66. break
  67. }
  68. queues[donorIdx].max--
  69. queues[recvIdx].max++
  70. state.Active = true
  71. }
  72. applyRebalanceQueue(&budget.Refinement, queues[0])
  73. applyRebalanceQueue(&budget.Record, queues[1])
  74. applyRebalanceQueue(&budget.Decode, queues[2])
  75. if state.Active {
  76. state.Adjustments = BudgetRebalanceAdjustments{
  77. Refinement: budget.Refinement.RebalanceDelta,
  78. Record: budget.Record.RebalanceDelta,
  79. Decode: budget.Decode.RebalanceDelta,
  80. }
  81. budget.Rebalance = state
  82. }
  83. return budget
  84. }
  85. func applyRebalanceQueue(queue *BudgetQueue, state rebalanceQueue) {
  86. if queue == nil {
  87. return
  88. }
  89. delta := state.max - state.baseMax
  90. queue.RebalanceDelta = delta
  91. if delta != 0 {
  92. queue.RebalancedMax = state.max
  93. } else {
  94. queue.RebalancedMax = 0
  95. }
  96. queue.EffectiveMax = effectiveBudget(budgetQueueLimit(*queue), queue.Preference)
  97. }
  98. func buildRebalanceState(policy Policy) BudgetRebalance {
  99. state := BudgetRebalance{
  100. Mode: "conservative",
  101. MaxShift: 1,
  102. }
  103. profile := strings.ToLower(strings.TrimSpace(policy.Profile))
  104. intent := strings.ToLower(strings.TrimSpace(policy.Intent))
  105. strategy := strings.ToLower(strings.TrimSpace(policy.RefinementStrategy))
  106. protect := map[string]bool{}
  107. favor := map[string]float64{
  108. "refinement": 1.0,
  109. "record": 1.0,
  110. "decode": 1.0,
  111. }
  112. reasons := make([]string, 0, 6)
  113. addReason := func(tag string) {
  114. if tag == "" {
  115. return
  116. }
  117. for _, r := range reasons {
  118. if r == tag {
  119. return
  120. }
  121. }
  122. reasons = append(reasons, tag)
  123. }
  124. legacy := strings.Contains(profile, "legacy")
  125. if legacy {
  126. state.MaxShift = 0
  127. addReason("profile:legacy")
  128. }
  129. if strings.Contains(profile, "archive") {
  130. protect["record"] = true
  131. favor["record"] += 0.3
  132. addReason("profile:archive")
  133. addReason("protect:record")
  134. }
  135. if strings.Contains(profile, "digital") {
  136. protect["decode"] = true
  137. favor["decode"] += 0.3
  138. addReason("profile:digital")
  139. addReason("protect:decode")
  140. }
  141. if strings.Contains(profile, "aggressive") {
  142. favor["refinement"] += 0.35
  143. if !legacy {
  144. state.MaxShift = maxInt(state.MaxShift, 2)
  145. }
  146. addReason("profile:aggressive")
  147. addReason("favor:refinement")
  148. }
  149. if strings.Contains(intent, "wideband") || strings.Contains(intent, "surveillance") {
  150. favor["refinement"] += 0.25
  151. if !legacy {
  152. state.MaxShift = maxInt(state.MaxShift, 2)
  153. }
  154. addReason("intent:wideband")
  155. addReason("favor:refinement")
  156. }
  157. if strings.Contains(intent, "archive") || strings.Contains(intent, "record") {
  158. protect["record"] = true
  159. addReason("intent:archive")
  160. addReason("protect:record")
  161. }
  162. if strings.Contains(intent, "decode") || strings.Contains(intent, "digital") || strings.Contains(intent, "hunt") {
  163. protect["decode"] = true
  164. addReason("intent:decode")
  165. addReason("protect:decode")
  166. }
  167. if strings.Contains(strategy, "archive") {
  168. protect["record"] = true
  169. addReason("strategy:archive")
  170. addReason("protect:record")
  171. }
  172. if strings.Contains(strategy, "digital") {
  173. protect["decode"] = true
  174. addReason("strategy:digital")
  175. addReason("protect:decode")
  176. }
  177. if strings.Contains(strategy, "multi") {
  178. favor["refinement"] += 0.2
  179. addReason("strategy:multi-resolution")
  180. addReason("favor:refinement")
  181. }
  182. state.Protect = mapKeysSorted(protect)
  183. state.Favor = favorKeysSorted(favor)
  184. state.Reasons = reasons
  185. state.favorWeights = favor
  186. state.protectMap = protect
  187. return state
  188. }
  189. func pickRebalanceReceiver(queues []rebalanceQueue) int {
  190. best := -1
  191. bestScore := 0.0
  192. for i := range queues {
  193. q := &queues[i]
  194. if q.baseMax <= 0 || q.max <= 0 {
  195. continue
  196. }
  197. if !pressureIsReceiver(q.pressure) {
  198. continue
  199. }
  200. score := pressureScore(q.pressure) * q.favor
  201. if best == -1 || score > bestScore {
  202. best = i
  203. bestScore = score
  204. }
  205. }
  206. return best
  207. }
  208. func pickRebalanceDonor(queues []rebalanceQueue) int {
  209. best := -1
  210. bestScore := 0.0
  211. for i := range queues {
  212. q := &queues[i]
  213. if q.baseMax <= 1 || q.max <= 1 {
  214. continue
  215. }
  216. if q.protect {
  217. continue
  218. }
  219. if !pressureIsDonor(q.pressure) {
  220. continue
  221. }
  222. score := pressureScore(q.pressure)
  223. if best == -1 || score < bestScore {
  224. best = i
  225. bestScore = score
  226. }
  227. }
  228. return best
  229. }
  230. func pressureIsReceiver(pressure BudgetPressure) bool {
  231. if pressure.Pressure >= 1.15 {
  232. return true
  233. }
  234. switch pressure.Level {
  235. case "high", "critical":
  236. return true
  237. default:
  238. return false
  239. }
  240. }
  241. func pressureIsDonor(pressure BudgetPressure) bool {
  242. if pressure.Level == "blocked" {
  243. return false
  244. }
  245. if pressure.Pressure == 0 && pressure.Demand == 0 {
  246. return true
  247. }
  248. if pressure.Pressure > 0 && pressure.Pressure <= 0.85 {
  249. return true
  250. }
  251. switch pressure.Level {
  252. case "steady", "idle":
  253. return true
  254. default:
  255. return false
  256. }
  257. }
  258. func pressureScore(pressure BudgetPressure) float64 {
  259. if pressure.Pressure > 0 {
  260. return pressure.Pressure
  261. }
  262. switch pressure.Level {
  263. case "critical":
  264. return 1.6
  265. case "high":
  266. return 1.2
  267. case "elevated":
  268. return 0.9
  269. case "steady":
  270. return 0.6
  271. case "idle":
  272. return 0.0
  273. default:
  274. return 0.0
  275. }
  276. }
  277. func mapKeysSorted(values map[string]bool) []string {
  278. if len(values) == 0 {
  279. return nil
  280. }
  281. keys := make([]string, 0, len(values))
  282. for k, ok := range values {
  283. if ok {
  284. keys = append(keys, k)
  285. }
  286. }
  287. sortStrings(keys)
  288. return keys
  289. }
  290. func favorKeysSorted(weights map[string]float64) []string {
  291. keys := make([]string, 0, len(weights))
  292. for k, v := range weights {
  293. if v > 1.01 {
  294. keys = append(keys, k)
  295. }
  296. }
  297. sortStrings(keys)
  298. return keys
  299. }
  300. func sortStrings(values []string) {
  301. if len(values) <= 1 {
  302. return
  303. }
  304. for i := 0; i < len(values)-1; i++ {
  305. for j := i + 1; j < len(values); j++ {
  306. if values[j] < values[i] {
  307. values[i], values[j] = values[j], values[i]
  308. }
  309. }
  310. }
  311. }
  312. func (r *BudgetRebalance) favorWeight(queue string) float64 {
  313. if r == nil {
  314. return 1.0
  315. }
  316. if r.favorWeights != nil {
  317. if v, ok := r.favorWeights[queue]; ok {
  318. return v
  319. }
  320. }
  321. return 1.0
  322. }
  323. func (r *BudgetRebalance) protects(queue string) bool {
  324. if r == nil {
  325. return false
  326. }
  327. if r.protectMap != nil {
  328. if v, ok := r.protectMap[queue]; ok {
  329. return v
  330. }
  331. }
  332. return false
  333. }
  334. func maxInt(a, b int) int {
  335. if a > b {
  336. return a
  337. }
  338. return b
  339. }