1use crate::clause::Clause;
2use crate::community::{Communities, CommunityOrdering, LouvainDetector, VariableIncidenceGraph};
3#[allow(unused_imports)]
9use crate::prelude::*;
10
11#[derive(Debug, Clone)]
13pub struct CommunityPartition {
14 partitions: Vec<Vec<usize>>,
16 communities: Communities,
18 num_communities: usize,
20}
21
22impl CommunityPartition {
23 pub fn from_clauses(num_vars: usize, clauses: &[Clause]) -> Self {
25 let vig = VariableIncidenceGraph::from_clauses(num_vars, clauses);
27 let detector = LouvainDetector::default();
28 let communities = detector.detect(&vig);
29 let num_communities = communities.num_communities();
30
31 let mut partitions: Vec<Vec<usize>> = vec![Vec::new(); num_communities];
33
34 for (idx, clause) in clauses.iter().enumerate() {
35 if let Some(primary_comm) = Self::get_primary_community(clause, &communities)
36 && primary_comm < num_communities
37 {
38 partitions[primary_comm].push(idx);
39 }
40 }
41
42 Self {
43 partitions,
44 communities,
45 num_communities,
46 }
47 }
48
49 fn get_primary_community(clause: &Clause, communities: &Communities) -> Option<usize> {
51 if clause.lits.is_empty() {
52 return None;
53 }
54
55 let mut comm_counts: HashMap<usize, usize> = HashMap::new();
56
57 for lit in &clause.lits {
58 let var = lit.var().index();
59 let comm = communities.community(var);
60 *comm_counts.entry(comm).or_insert(0) += 1;
61 }
62
63 comm_counts
64 .into_iter()
65 .max_by_key(|(_, count)| *count)
66 .map(|(comm, _)| comm)
67 }
68
69 pub fn get_partition(&self, community: usize) -> &[usize] {
71 if community < self.partitions.len() {
72 &self.partitions[community]
73 } else {
74 &[]
75 }
76 }
77
78 pub fn num_communities(&self) -> usize {
80 self.num_communities
81 }
82
83 pub fn community(&self, var: usize) -> usize {
85 self.communities.community(var)
86 }
87
88 pub fn partition_sizes(&self) -> Vec<usize> {
90 self.partitions.iter().map(|p| p.len()).collect()
91 }
92
93 pub fn total_clauses(&self) -> usize {
95 self.partitions.iter().map(|p| p.len()).sum()
96 }
97
98 pub fn communities(&self) -> &Communities {
100 &self.communities
101 }
102
103 pub fn create_ordering(&self) -> CommunityOrdering {
105 CommunityOrdering::new(self.communities.clone())
106 }
107}
108
109#[derive(Debug, Clone, Default)]
111pub struct PartitionStats {
112 pub num_communities: usize,
114 pub total_clauses: usize,
116 pub partition_sizes: Vec<usize>,
118 pub avg_partition_size: f64,
120 pub max_partition_size: usize,
122 pub min_partition_size: usize,
124 pub modularity: f64,
126}
127
128impl PartitionStats {
129 pub fn from_partition(partition: &CommunityPartition) -> Self {
131 let sizes = partition.partition_sizes();
132 let total = partition.total_clauses();
133 let avg = if !sizes.is_empty() {
134 total as f64 / sizes.len() as f64
135 } else {
136 0.0
137 };
138
139 Self {
140 num_communities: partition.num_communities(),
141 total_clauses: total,
142 partition_sizes: sizes.clone(),
143 avg_partition_size: avg,
144 max_partition_size: sizes.iter().copied().max().unwrap_or(0),
145 min_partition_size: sizes.iter().copied().min().unwrap_or(0),
146 modularity: partition.communities().modularity(),
147 }
148 }
149
150 pub fn display(&self) -> String {
152 format!(
153 "Community Partition Statistics:\n\
154 - Communities: {}\n\
155 - Total Clauses: {}\n\
156 - Avg Partition Size: {:.2}\n\
157 - Size Range: [{}, {}]\n\
158 - Modularity: {:.4}",
159 self.num_communities,
160 self.total_clauses,
161 self.avg_partition_size,
162 self.min_partition_size,
163 self.max_partition_size,
164 self.modularity
165 )
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::community::{LouvainDetector, VariableIncidenceGraph};
173 use crate::literal::{Lit, Var};
174
175 fn make_lit(var: usize, sign: bool) -> Lit {
176 let v = Var::new(var as u32);
177 if sign { Lit::pos(v) } else { Lit::neg(v) }
178 }
179
180 #[test]
181 fn test_partition_creation() {
182 let clauses = vec![
183 Clause::original(vec![make_lit(0, false), make_lit(1, false)]),
184 Clause::original(vec![make_lit(0, false), make_lit(1, true)]),
185 Clause::original(vec![make_lit(2, false), make_lit(3, false)]),
186 Clause::original(vec![make_lit(2, true), make_lit(3, false)]),
187 ];
188
189 let partition = CommunityPartition::from_clauses(4, &clauses);
190
191 assert!(partition.num_communities() > 0);
192 assert_eq!(partition.total_clauses(), 4);
193 }
194
195 #[test]
196 fn test_get_partition() {
197 let clauses = vec![
198 Clause::original(vec![make_lit(0, false), make_lit(1, false)]),
199 Clause::original(vec![make_lit(2, false), make_lit(3, false)]),
200 ];
201
202 let partition = CommunityPartition::from_clauses(4, &clauses);
203
204 for i in 0..partition.num_communities() {
205 let part = partition.get_partition(i);
206 assert!(part.len() <= 2);
207 }
208 }
209
210 #[test]
211 fn test_primary_community() {
212 let test_clauses = vec![
214 Clause::original(vec![make_lit(0, false), make_lit(0, true)]),
215 Clause::original(vec![make_lit(1, false)]),
216 ];
217
218 let vig = VariableIncidenceGraph::from_clauses(2, &test_clauses);
219 let detector = LouvainDetector::default();
220 let communities = detector.detect(&vig);
221
222 let clause = Clause::original(vec![
224 make_lit(0, false),
225 make_lit(0, true),
226 make_lit(1, false),
227 ]);
228
229 let primary = CommunityPartition::get_primary_community(&clause, &communities);
230 assert!(primary.is_some());
231 assert_eq!(primary, Some(communities.community(0)));
233 }
234
235 #[test]
236 fn test_partition_sizes() {
237 let clauses = vec![
238 Clause::original(vec![make_lit(0, false), make_lit(1, false)]),
239 Clause::original(vec![make_lit(2, false), make_lit(3, false)]),
240 Clause::original(vec![make_lit(4, false), make_lit(5, false)]),
241 ];
242
243 let partition = CommunityPartition::from_clauses(6, &clauses);
244 let sizes = partition.partition_sizes();
245
246 assert_eq!(sizes.iter().sum::<usize>(), 3);
247 }
248
249 #[test]
250 fn test_create_ordering() {
251 let clauses = vec![
252 Clause::original(vec![make_lit(0, false), make_lit(1, false)]),
253 Clause::original(vec![make_lit(2, false), make_lit(3, false)]),
254 ];
255
256 let partition = CommunityPartition::from_clauses(4, &clauses);
257 let ordering = partition.create_ordering();
258
259 assert_eq!(ordering.ordering().len(), 4);
260 }
261
262 #[test]
263 fn test_partition_stats() {
264 let clauses = vec![
265 Clause::original(vec![make_lit(0, false), make_lit(1, false)]),
266 Clause::original(vec![make_lit(2, false), make_lit(3, false)]),
267 Clause::original(vec![make_lit(4, false), make_lit(5, false)]),
268 ];
269
270 let partition = CommunityPartition::from_clauses(6, &clauses);
271 let stats = PartitionStats::from_partition(&partition);
272
273 assert_eq!(stats.total_clauses, 3);
274 assert!(stats.avg_partition_size > 0.0);
275 assert!(stats.modularity >= 0.0);
276 }
277
278 #[test]
279 fn test_empty_partition() {
280 let partition = CommunityPartition::from_clauses(5, &[]);
281
282 assert_eq!(partition.total_clauses(), 0);
283 assert_eq!(partition.num_communities(), 5);
284 }
285
286 #[test]
287 fn test_single_clause_partition() {
288 let clauses = vec![Clause::original(vec![
289 make_lit(0, false),
290 make_lit(1, false),
291 ])];
292
293 let partition = CommunityPartition::from_clauses(2, &clauses);
294
295 assert_eq!(partition.total_clauses(), 1);
296 }
297
298 #[test]
299 fn test_community_lookup() {
300 let clauses = vec![
301 Clause::original(vec![make_lit(0, false), make_lit(1, false)]),
302 Clause::original(vec![make_lit(2, false), make_lit(3, false)]),
303 ];
304
305 let partition = CommunityPartition::from_clauses(4, &clauses);
306
307 for var in 0..4 {
309 let comm = partition.community(var);
310 assert!(comm < partition.num_communities());
311 }
312 }
313}