import math import numpy as np # Let's list the raw camera data # Raw specifications: Price, ISO, AF Point, Sensor (ordinal) raw_data = [ # [Nama, Price, ISO, AF, Sensor] ["Canon M10", 80000, 25600, 49, "APS-C"], ["Sony A5000", 90000, 25600, 25, "APS-C"], ["Canon M3", 100000, 25600, 49, "APS-C"], ["Sony A6000", 110000, 25600, 179, "APS-C"], ["Canon M50", 130000, 51200, 143, "APS-C"], ["Sony A6300", 155000, 51200, 425, "APS-C"], ["Fujifilm XA3", 80000, 25600, 77, "APS-C"], ["Sony A6400", 185000, 51200, 425, "APS-C"], ["Fujifilm XA5", 90000, 25600, 77, "APS-C"], ["Nikon J5", 75000, 12800, 171, "1-inch"], ["Fujifilm XT20", 145000, 51200, 325, "APS-C"], ["Canon 500D", 55000, 12800, 9, "APS-C"], ["Canon 60D", 100000, 12800, 9, "APS-C"], ["Canon 1100D", 55000, 12800, 9, "APS-C"], ["Canon 80D", 140000, 25600, 45, "APS-C"], ["Canon 600D", 80000, 12800, 9, "APS-C"], ["Canon 6D", 165000, 102400, 61, "Full Frame"], ["Canon 550D", 75000, 12800, 9, "APS-C"], ["Canon 5D Mark III", 215000, 102400, 61, "Full Frame"] ] price_min = 55000 price_max = 215000 iso_max = 102400 af_max = 425 print("--- SCALE 0 to 1 (x1) ---") print("| No | Nama Kamera | Merek | Price Score | ISO Score | AF Score | Sensor Score |") print("|---|---|---|---|---|---|---|") processed_data = [] sensor_mapping = { "Full Frame": 1.0, "APS-C": 0.7, "1-inch": 0.5 } for idx, cam in enumerate(raw_data): name = cam[0] brand = name.split()[0] price = cam[1] iso = cam[2] af = cam[3] sensor = cam[4] price_score = ((price_max - price) / (price_max - price_min)) * 1.0 iso_score = (iso / iso_max) * 1.0 af_score = (af / af_max) * 1.0 sensor_score = sensor_mapping[sensor] # Let's round to 4 decimals for exact tracking, and 2 decimals for display # Check what rounding was used. # In table 4.9: Canon M10: 8.44 (rounded from 8.4375), Sony A6400: 1.88 (rounded from 1.875) # AF score: Canon M10 49/425 * 10 = 1.1529... -> 1.15 # So it is rounded to 2 decimal places. ps_rnd = round(price_score, 2) is_rnd = round(iso_score, 2) af_rnd = round(af_score, 2) ss_rnd = round(sensor_score, 2) print(f"| {idx+1} | {name} | {brand} | {ps_rnd:.2f} | {is_rnd:.2f} | {af_rnd:.2f} | {ss_rnd:.2f} |") processed_data.append([name, ps_rnd, is_rnd, af_rnd, ss_rnd]) # Let's compute TOPSIS for Outdoor - Day (Siang) using the rounded table values # Bobot = [0.30, 0.15, 0.25, 0.30] # Jenis = [cost (since PriceScore is benefit, wait! Is PriceScore cost or benefit in TOPSIS?) # In RekomendasiController, PriceScore is defined as cost: $jenis = ['cost', 'benefit', 'benefit', 'benefit']; # BUT wait, PriceScore already has (PriceMax - Price) / (PriceMax - PriceMin), so higher is cheaper (better). # If PriceScore is marked as 'cost' in $jenis, then Step 4 (Ideal Pos/Neg) does: # for cost kriteria: idealPos is MIN(col), idealNeg is MAX(col). # So idealPos will select the minimum of PriceScore (which corresponds to highest price, e.g. 0.00), and idealNeg will select maximum of PriceScore (which corresponds to lowest price, e.g. 1.00). # Let's verify if that matches the PDF! # In PDF: # Solusi Ideal Positif A+ = [0.000000, 0.079823, 0.140025, 0.094444] # Here, the first element of A+ is indeed 0.000000 (which is the minimum of weighted normal for PriceScore). # And the first element of A- is indeed 0.098277 (which is the maximum of weighted normal for PriceScore, corresponding to Canon 500D/1100D with PriceScore 10.00). # Yes! The code in RekomendasiController marks it as 'cost', which means it takes min for idealPos and max for idealNeg. # Let's perform this calculation. scores = np.array([[row[1], row[2], row[3], row[4]] for row in processed_data]) m, n = scores.shape # Euclidean Divider dividers = [] for j in range(n): dividers.append(math.sqrt(sum(scores[i][j]**2 for i in range(m)))) print("\n--- Dividers ---") for j, div in enumerate(dividers): print(f"C_{j+1} Divider: {div:.4f}") # Normalization r = np.zeros((m, n)) for i in range(m): for j in range(n): r[i][j] = scores[i][j] / dividers[j] # Weighted Normalized Matrix bobot = [0.30, 0.15, 0.25, 0.30] y = np.zeros((m, n)) for i in range(m): for j in range(n): y[i][j] = r[i][j] * bobot[j] # Ideal solutions # C1 is cost, C2-C4 is benefit ideal_pos = [min(y[:, 0]), max(y[:, 1]), max(y[:, 2]), max(y[:, 3])] ideal_neg = [max(y[:, 0]), min(y[:, 1]), min(y[:, 2]), min(y[:, 3])] print("\nA+:", [f"{val:.6f}" for val in ideal_pos]) print("A-:", [f"{val:.6f}" for val in ideal_neg]) # Distances and Preferences results = [] for i in range(m): dp = math.sqrt(sum((y[i][j] - ideal_pos[j])**2 for j in range(n))) dm = math.sqrt(sum((y[i][j] - ideal_neg[j])**2 for j in range(n))) v = dm / (dp + dm) if (dp + dm) > 0 else 0.0 results.append((processed_data[i][0], scores[i], dp, dm, v)) # Sort by preference results.sort(key=lambda x: x[4], reverse=True) print("\n--- Ranking for Outdoor - Day ---") for rank, res in enumerate(results): name, original_scores, dp, dm, v = res print(f"Rank {rank+1}: {name} | Scores: {original_scores} | D+: {dp:.4f} | D-: {dm:.4f} | V: {v:.6f}")