define_module/
define_module.rs

1use std::{
2    io::{Write, stderr},
3    sync::Arc,
4};
5
6use algebra::{
7    AdemAlgebra, Algebra, GeneratedAlgebra, module::FDModule, steenrod_evaluator::SteenrodEvaluator,
8};
9use anyhow::anyhow;
10use bivec::BiVec;
11use fp::{
12    prime::{Prime, ValidPrime},
13    vector::FpVector,
14};
15use rustc_hash::FxHashMap as HashMap;
16use serde_json::{Value, json};
17
18pub fn get_gens() -> anyhow::Result<BiVec<Vec<String>>> {
19    ext::utils::init_logging()?;
20
21    // Query for generators
22    eprintln!("Input generators. Press return to finish.");
23    stderr().flush()?;
24
25    let mut gens: BiVec<Vec<_>> = BiVec::new(0);
26    loop {
27        let gen_deg: Option<i32> = query::optional("Generator degree", str::parse);
28        if gen_deg.is_none() {
29            eprintln!("This is the list of generators and degrees:");
30            for (i, deg_i_gens) in gens.iter_enum() {
31                for g in deg_i_gens.iter() {
32                    eprint!("({i}, {g}) ");
33                }
34            }
35            eprintln!();
36            if query::yes_no("Is it okay?") {
37                break;
38            } else {
39                if query::yes_no("Start over?") {
40                    gens = BiVec::new(0);
41                }
42                continue;
43            }
44        }
45
46        let gen_deg = gen_deg.unwrap();
47
48        gens.extend_negative(gen_deg, Vec::new());
49        gens.extend_with(gen_deg, |_| Vec::new());
50
51        let gen_name = query::with_default(
52            "Generator name",
53            &format!("x{gen_deg}{}", gens[gen_deg].len()).replace('-', "_"),
54            |x| {
55                match x.chars().next() {
56                    Some(a) => {
57                        if !a.is_alphabetic() {
58                            return Err("variable name must start with a letter".to_string());
59                        }
60                    }
61                    None => return Err("Variable name cannot be empty".to_string()),
62                };
63                for c in x.chars() {
64                    if !c.is_alphanumeric() && c != '_' {
65                        return Err(format!(
66                            "Variable name cannot contain {c}. Should be alphanumeric and '_'"
67                        ));
68                    }
69                }
70                Ok(x.to_string())
71            },
72        );
73        gens[gen_deg].push(gen_name);
74    }
75    Ok(gens)
76}
77
78pub fn gens_to_json(gens: &BiVec<Vec<String>>) -> serde_json::Value {
79    let mut gens_json = json!({});
80    for (i, deg_i_gens) in gens.iter_enum() {
81        for g in deg_i_gens {
82            gens_json[g] = json!(i);
83        }
84    }
85    gens_json
86}
87
88pub fn interactive_module_define_fdmodule(
89    output_json: &mut Value,
90    p: ValidPrime,
91) -> anyhow::Result<()> {
92    output_json["p"] = Value::from(p.as_u32());
93    let algebra = Arc::new(AdemAlgebra::new(p, false));
94
95    let gens = get_gens()?;
96    let min_degree = gens.min_degree();
97    let max_degree = gens.len();
98
99    algebra.compute_basis(max_degree - min_degree);
100
101    let mut graded_dim = BiVec::with_capacity(min_degree, max_degree);
102    for i in gens.iter().map(Vec::len) {
103        graded_dim.push(i);
104    }
105
106    let mut module = FDModule::new(Arc::clone(&algebra), String::new(), graded_dim);
107
108    for (i, deg_i_gens) in gens.iter_enum() {
109        for (j, g) in deg_i_gens.iter().enumerate() {
110            module.set_basis_element_name(i, j, g.to_string());
111        }
112    }
113
114    eprintln!(
115        "Input actions. Write the value of the action in the form 'a x0 + b x1 + ...' where a, b \
116         are non-negative integers and x0, x1 are names of the generators. The coefficient can be \
117         omitted if it is 1"
118    );
119
120    let len = gens.len();
121    for input_deg in gens.range().rev() {
122        for output_deg in (input_deg + 1)..len {
123            let op_deg = output_deg - input_deg;
124            if gens[output_deg].is_empty() {
125                continue;
126            }
127            for op_idx in algebra.generators(op_deg) {
128                for input_idx in 0..gens[input_deg].len() {
129                    let output = query::raw(
130                        &format!(
131                            "{} {}",
132                            algebra.basis_element_to_string(op_deg, op_idx),
133                            gens[input_deg][input_idx]
134                        ),
135                        |expr| {
136                            let mut result = vec![0; gens[output_deg].len()];
137                            if expr == "0" {
138                                return Ok(result);
139                            }
140                            for term in expr.split('+') {
141                                let term = term.trim();
142                                let (coef, g) = match term.split_once(' ') {
143                                    Some((coef, g)) => (str::parse::<u32>(coef)?, g),
144                                    None => (1, term),
145                                };
146
147                                if let Some(gen_idx) = gens[output_deg].iter().position(|d| d == g)
148                                {
149                                    result[gen_idx] += coef;
150                                } else {
151                                    return Err(anyhow!("No generator {g} in degree {output_deg}"));
152                                }
153                            }
154
155                            Ok(result)
156                        },
157                    );
158
159                    module.set_action(op_deg, op_idx, input_deg, input_idx, &output);
160                }
161            }
162            module.extend_actions(input_deg, output_deg);
163            module.check_validity(input_deg, output_deg)?;
164        }
165    }
166
167    module.to_json(output_json);
168    Ok(())
169}
170
171/// Given a string representation of an element in an algebra together with a generator, multiply
172/// each term on the right with the generator.
173fn replace(algebra_elt: &str, g: &str) -> String {
174    algebra_elt.replace('+', &format!("{g} +")) + " " + g
175}
176
177pub fn interactive_module_define_fpmodule(
178    output_json: &mut Value,
179    p: ValidPrime,
180) -> anyhow::Result<()> {
181    let gens = get_gens()?;
182    let min_degree = gens.min_degree();
183    let max_degree = gens.len();
184
185    let ev = SteenrodEvaluator::new(p);
186
187    let mut graded_dim = BiVec::with_capacity(min_degree, max_degree);
188    for i in gens.iter().map(Vec::len) {
189        graded_dim.push(i);
190    }
191
192    eprintln!("Input relations");
193    match p.as_u32() {
194        2 => eprintln!("Write relations in the form 'Sq6 * Sq2 * x + Sq7 * y'"),
195        _ => eprintln!(
196            "Write relations in the form 'Q5 * P(5) * x + 2 * P(1, 3) * Q2 * y', where P(...) and \
197             Qi are Milnor basis elements."
198        ),
199    }
200
201    let mut degree_lookup = HashMap::default();
202    for (i, deg_i_gens) in gens.iter_enum() {
203        for g in deg_i_gens.iter() {
204            degree_lookup.insert(g.clone(), i);
205        }
206    }
207
208    let mut adem_relations = Vec::new();
209    let mut milnor_relations = Vec::new();
210
211    loop {
212        let relation = query::raw("Enter relation", |rel| {
213            let result = ev.evaluate_module_adem(rel)?;
214
215            if result.is_empty() {
216                return Ok(result);
217            }
218
219            // Check that the generators exist and the terms all have the same degree
220            let degrees = result
221                .iter()
222                .map(|(g, (op_deg, _))| {
223                    degree_lookup
224                        .get(g)
225                        .map(|d| d + op_deg)
226                        .ok_or_else(|| anyhow!("Unknown generator: {g}"))
227                })
228                .collect::<Result<Vec<_>, _>>()?;
229            for window in degrees.windows(2) {
230                if window[0] != window[1] {
231                    return Err(anyhow!(
232                        "Relation terms have different degrees: {} and {}",
233                        window[0],
234                        window[1],
235                    ));
236                }
237            }
238
239            Ok(result)
240        });
241
242        if relation.is_empty() {
243            break;
244        }
245
246        let mut adem_relation = String::new();
247        let mut milnor_relation = String::new();
248
249        let mut milnor_op = FpVector::new(p, 0);
250        for (g, (op_deg, adem_op)) in relation.iter() {
251            if adem_op.is_zero() {
252                continue;
253            }
254            if !adem_relation.is_empty() {
255                adem_relation += " + ";
256                milnor_relation += " + ";
257            }
258            milnor_op.set_scratch_vector_size(adem_op.len());
259            ev.adem_to_milnor(&mut milnor_op, 1, *op_deg, adem_op);
260
261            adem_relation += &replace(&ev.adem.element_to_string(*op_deg, adem_op.as_slice()), g);
262            milnor_relation += &replace(
263                &ev.milnor.element_to_string(*op_deg, milnor_op.as_slice()),
264                g,
265            );
266        }
267        if !adem_relation.is_empty() {
268            adem_relations.push(Value::String(adem_relation));
269            milnor_relations.push(Value::String(milnor_relation));
270        }
271    }
272
273    output_json["p"] = Value::from(p.as_u32());
274    output_json["type"] = Value::String("finitely presented module".to_owned());
275    for (i, deg_i_gens) in gens.iter_enum() {
276        for g in deg_i_gens {
277            output_json["gens"][g] = Value::from(i);
278        }
279    }
280    output_json["adem_relations"] = Value::Array(adem_relations);
281    output_json["milnor_relations"] = Value::Array(milnor_relations);
282    Ok(())
283}
284
285fn main() -> anyhow::Result<()> {
286    let module_type = query::with_default(
287        "Input module type (default 'finite dimensional module'):\n (fd) - finite dimensional \
288         module \n (fp) - finitely presented module\n",
289        "fd",
290        |x| match x {
291            "fd" | "fp" => Ok(x.to_string()),
292            _ => Err(format!("Invalid type '{x}'. Type must be 'fd' or 'fp'")),
293        },
294    );
295
296    let p: ValidPrime = query::with_default("p", "2", str::parse);
297    let mut output_json = json!({});
298
299    eprintln!("module_type: {module_type}");
300    match &*module_type {
301        "fd" => interactive_module_define_fdmodule(&mut output_json, p)?,
302        "fp" => interactive_module_define_fpmodule(&mut output_json, p)?,
303        _ => unreachable!(),
304    }
305    println!("{output_json}");
306    Ok(())
307}