plotNumberOfDCP.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import argparse
  2. import pandas as pd
  3. import numpy as np
  4. import matplotlib.pyplot as plt
  5. from model_properties import model_properties
  6. # ==========================================
  7. # Model Formulas
  8. # ==========================================
  9. def model_dLI(width, depth, n):
  10. """
  11. dLI Model Formula:
  12. DCP = ((n + 1)(d - 1) + 1) * floor((w - 1) / n) + remaining
  13. remaining = (n_2 + 1)(d - 1) + 1 if n_2 > 0 else 0
  14. where n_2 = (w - 1) % n
  15. """
  16. w = np.atleast_1d(width)
  17. d = np.atleast_1d(depth)
  18. n_val = np.atleast_1d(n)
  19. # Broadcast scalar n if w/d are arrays
  20. if len(n_val) == 1 and len(w) > 1:
  21. n_val = np.full_like(w, n_val[0])
  22. # Ensure n >= 1
  23. n_val = np.maximum(n_val, 1)
  24. # Main Term
  25. floor_term = np.floor((w - 1) / n_val)
  26. term_main = ((n_val + 1) * (d - 1) + 1) * floor_term
  27. # Remaining Term
  28. n_2 = (w - 1) % n_val
  29. term_rem = (n_2 + 1) * (d - 1) + 1
  30. term_rem = np.where(n_2 > 0, term_rem, 0)
  31. return term_main + term_rem
  32. def model_dHI(width, depth, n):
  33. """
  34. dHI Model Formula:
  35. DCP = c1 + ci * floor((w - n - 1) / n) + remaining
  36. c1 = (2n + 1)(d - 1) + 1
  37. ci = (2n + 2)(d - 1) + 1
  38. remaining = (2n_2 + 1)(d - 1) + 1 if n_2 > 0 else 0
  39. where n_2 = (w - n - 1) % n
  40. """
  41. w = np.atleast_1d(width)
  42. d = np.atleast_1d(depth)
  43. n_val = np.atleast_1d(n)
  44. if len(n_val) == 1 and len(w) > 1:
  45. n_val = np.full_like(w, n_val[0])
  46. # Safety: clamp n to width-1 to preserve physical meaning (chunk size cannot exceed width)
  47. # This prevents negative floor values when w < n+1
  48. n_eff = np.where(w > 1, np.minimum(n_val, w - 1), 1)
  49. # First term (c1)
  50. term_c1 = (2 * n_eff + 1) * (d - 1) + 1
  51. # Intermediate term (ci)
  52. val_for_floor = w - n_eff - 1
  53. floor_term = np.floor(val_for_floor / n_eff)
  54. term_ci = ((2 * n_eff + 2) * (d - 1) + 1) * floor_term
  55. # Remaining term
  56. n_2 = val_for_floor % n_eff
  57. term_rem = (2 * n_2 + 1) * (d - 1) + 1
  58. term_rem = np.where(n_2 > 0, term_rem, 0)
  59. return term_c1 + term_ci + term_rem
  60. MODELS = {
  61. 'dLI': model_dLI,
  62. 'dHI': model_dHI,
  63. }
  64. def get_model_func(model_name):
  65. return MODELS.get(model_name)
  66. # ==========================================
  67. # Execution Modes
  68. # ==========================================
  69. def run_vary_n(args):
  70. """Mode 1: Fix W/D, Vary n"""
  71. print(f"Running 'Vary N' | Model: {args.model}, Width: {args.width}, Depth: {args.depth}")
  72. func = get_model_func(args.model)
  73. if not func:
  74. print(f"Error: Model '{args.model}' not found.")
  75. return
  76. n_vals = np.arange(1, args.limit + 1)
  77. Y = func(args.width, args.depth, n_vals)
  78. plt.figure(figsize=(10, 6))
  79. plt.plot(n_vals, Y, marker='o', label=f'{args.model}: W={args.width}, D={args.depth}')
  80. plt.yscale('log')
  81. plt.xlabel('n (Models per cycle)')
  82. plt.ylabel('Number of DCP calls')
  83. plt.title(f'#DCP Calls vs n for {args.model}')
  84. plt.legend()
  85. plt.grid(True, which="both", ls="--")
  86. plt.tight_layout()
  87. plt.show()
  88. def run_vary_dim(args):
  89. """Mode 2: Fix n, Vary Width/Depth (The 'Old Code' adaptation)"""
  90. print(f"Running 'Vary Dimensions' | Model: {args.model}, n: {args.n}")
  91. func = get_model_func(args.model)
  92. if not func:
  93. print(f"Error: Model '{args.model}' not found.")
  94. return
  95. # Generate grid of W and D
  96. w_vals = np.arange(2, args.limit + 1)
  97. d_vals = np.arange(2, args.limit + 1)
  98. width, depth = np.meshgrid(w_vals, d_vals)
  99. # Calculate X axis (Total Atomic Models)
  100. X = (width - 1) * (depth - 1) + 1
  101. # Calculate Y axis (DCP Calls)
  102. Y = func(width, depth, args.n)
  103. plt.figure(figsize=(10, 6))
  104. plt.scatter(X.flatten(), Y.flatten(), s=5, alpha=0.5, label=f'{args.model}, n={args.n}')
  105. plt.yscale('log')
  106. # plt.xscale('log') # Optional: log scale for X if desired
  107. plt.xlabel('Number of atomic models')
  108. plt.ylabel('Number of DCP calls')
  109. plt.title(f'#DCP Calls vs Atomic Models for {args.model}')
  110. plt.legend()
  111. plt.grid(True, which="both", ls="--")
  112. plt.tight_layout()
  113. plt.show()
  114. def clean_data(df):
  115. df["total_time"] = (
  116. df["simulation_time"]
  117. + df["creation_time"]
  118. + df["setup_time"]
  119. )
  120. possible_group_cols = ["depth", "width", "number", "total_time"]
  121. grouping_cols = [c for c in possible_group_cols if c in df.columns]
  122. def clean(group):
  123. Q1 = group["total_time"].quantile(0.25)
  124. Q3 = group["total_time"].quantile(0.75)
  125. IQR = Q3 - Q1
  126. lower = Q1 - 1.5 * IQR
  127. upper = Q3 + 1.5 * IQR
  128. return group[(group["total_time"] >= lower) & (group["total_time"] <= upper)]
  129. cleaned = (
  130. df.groupby(grouping_cols, group_keys=False)[grouping_cols]
  131. .apply(clean)
  132. )
  133. return cleaned
  134. def run_csv_mode(args):
  135. """Mode 3: Read CSV, calculate theoretical DCP, plot vs Runtime"""
  136. print(f"Running 'CSV Mode' | File: {args.file}")
  137. try:
  138. df = pd.read_csv(args.file)
  139. except FileNotFoundError:
  140. print(f"Error: File {args.file} not found.")
  141. return
  142. df = clean_data(df)
  143. # Create 'dcp_calls' column
  144. df['dcp_calls'] = 0.0
  145. # Determine model per row or globally
  146. if 'model_type' not in df.columns:
  147. df['model_type'] = args.model
  148. # Check for unknown models in CSV
  149. unique_models = df['model_type'].unique()
  150. if len(unique_models) > 1:
  151. print(f"Error: More than one model type found. Found: {unique_models}")
  152. return
  153. model = unique_models[0]
  154. if model not in MODELS:
  155. print(f"Error: CSV contains unknown model '{model}'.")
  156. return
  157. df['atomic_models'] = (df['width'] - 1) * (df['depth'] - 1) + 1
  158. # Calculate DCP calls by group (efficient for different models)
  159. for m_name, group in df.groupby('model_type'):
  160. func = get_model_func(m_name)
  161. # Fallback to default if model not found in dictionary
  162. if not func:
  163. func = get_model_func(args.model)
  164. # Use 'n' from CSV if available, otherwise CLI default
  165. n_vals = group['number'].values if 'number' in group.columns else args.n
  166. df.loc[group.index, 'dcp_calls'] = func(group['width'].values, group['depth'].values, n_vals)
  167. plt.figure(figsize=(10, 6))
  168. scatter = plt.scatter(df['dcp_calls'], df['total_time'],
  169. c=df['atomic_models'], cmap='viridis', alpha=0.7, edgecolors='k', s=50)
  170. plt.colorbar(scatter, label='# Atomic Models')
  171. plt.xlabel('Theoretical DCP Calls')
  172. plt.ylabel('Total Simulation Time (s)')
  173. plt.title(f'Run Time vs DCP Calls for {model}')
  174. plt.grid(True, linestyle='--', alpha=0.6)
  175. plt.tight_layout()
  176. plt.show()
  177. # ==========================================
  178. # CLI
  179. # ==========================================
  180. def main():
  181. parser = argparse.ArgumentParser(description="DCP Calculation Tool")
  182. subparsers = parser.add_subparsers(dest='mode', required=True, help='Operation Mode')
  183. # Common arguments
  184. parent_parser = argparse.ArgumentParser(add_help=False)
  185. parent_parser.add_argument('--model', type=str, default='dHI', choices=MODELS.keys(),
  186. help='Model formula to use (default: dHI)')
  187. # 1. Vary N
  188. parser_n = subparsers.add_parser('created', parents=[parent_parser], help='Fix Width and Depth, Vary number of models created per cycle')
  189. parser_n.add_argument('--width', '-w', type=int, default=100)
  190. parser_n.add_argument('--depth', '-d', type=int, default=100)
  191. parser_n.add_argument('--limit', '-l', type=int, default=100, help='Max n')
  192. # 2. Vary Dimensions
  193. parser_dim = subparsers.add_parser('dimensions', parents=[parent_parser], help='Fix n, Vary W/D')
  194. parser_dim.add_argument('--n', type=int, default=2)
  195. parser_dim.add_argument('--limit', '-l', type=int, default=100, help='Max width/depth')
  196. # 3. CSV
  197. parser_csv = subparsers.add_parser('time', parents=[parent_parser], help='Read CSV and plot DCP calls against simulation time')
  198. parser_csv.add_argument('--file', '-f', type=str)
  199. parser_csv.add_argument('--n', type=int, default=1, help='Default n if not in CSV')
  200. args = parser.parse_args()
  201. if args.mode == 'created':
  202. run_vary_n(args)
  203. elif args.mode == 'dimensions':
  204. run_vary_dim(args)
  205. elif args.mode == 'time':
  206. run_csv_mode(args)
  207. if __name__ == "__main__":
  208. main()