// FILE HOMSPACE.H: Declaration of class homspace
//////////////////////////////////////////////////////////////////////////
//
// Copyright 1990-2026 John Cremona
//
// This file is part of the eclib package.
//
// eclib is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.
//
// eclib is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License
// along with eclib; if not, write to the Free Software Foundation,
// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
//
//////////////////////////////////////////////////////////////////////////

#ifndef _ECLIB_HOMSPACE_H
#define _ECLIB_HOMSPACE_H      1
                           //flags that this file has been included

#include "linalg.h"  // uses/sets SCALAR_OPTION to fix scalar type and includes all LA headers
#include "convert.h"
#include "symb.h"

// If this is defined, the order of primes used for Wq and Tp
// operators is the natural order 2, 3, 5, 7, ..., mixing good and bad
// primes.  Otherwise the order is as in primelist (see moddata.h)
// with the bad primes first.  This is referred to in two places: the
// homspace method op_prime(i) which returns the i'th prime to be
// used; and in periods.cc where (when NEW_OP_ORDER is *not* set) some
// permutation of eigenvalues needs to be done.
#define NEW_OP_ORDER

class homspace;

class mat22 {  //2x2 matrix for linear fractional transformations
private:
  long a,b,c,d;
public:
  explicit mat22(long ia=0, long ib=0, long ic=0, long id=0)
    :a(ia),b(ib),c(ic),d(id){;}
  mat22(const mat22& m) :a(m.a),b(m.b),c(m.c),d(m.d)
  {;}
  void operator=(const mat22& m)
  {a=m.a; b=m.b; c=m.c; d=m.d;}
  void show(ostream& s) const
  {s << "[" << a << "," << b << ";"<< c << "," << d << "]";}
  rational operator()(const rational& q)const
  {
    long n=num(q),de=den(q);
    return rational(a*n+b*de,c*n+d*de);
  }
  modsym operator()(const modsym& m)const
  {
    return modsym((*this)(m.alpha()),(*this)(m.beta()));
  }
  svec operator()(const symb& s, const homspace* h)const;
  vec operator()(const symb& s, const homspace* h, const mat& m)const;
};

class matop {  // formal sum of 2x2 matrices representing
private:
  vector<mat22> mats;
  string the_name;
public:
  matop(long p, long n);   // constructor for hecke ops
  explicit matop(long p);  // constructor for heilbronn matrices
  matop() {;}
  matop(long a, long b, long c, long d);  // constructor for a single matrix
  long size() const {return mats.size();}
  mat22 operator[](long i) const {return mats[i];}
  string name() const  {return the_name;}
};

inline ostream& operator<< (ostream& s, const mat22& m)
{
   m.show(s);
   return s;
}

// a gmatop is a linear combination of matops
class gmatop {
 public:
  vector<matop> ops;
  vector<scalar> coeffs;
  gmatop() {;}
  explicit gmatop(const vector<matop>& Tlist, const vector<scalar> clist)
    :ops(Tlist), coeffs(clist)  {;}
  explicit gmatop(const vector<matop>& Tlist)
    :ops(Tlist), coeffs(vector<scalar>(Tlist.size(), scalar(1)))  {;}
  explicit gmatop(const matop& T)
    :ops({T}), coeffs({scalar(1)})  {;}
  void set_coeff(int i, const scalar& c) {coeffs[i] = c;}
  string name() const;
  int is_simple() const {return ops.size()==1 && coeffs[0]==1;}
};

class homspace :public symbdata {
  //private:
public:
  scalar modulus; // for linear algebra on boundary matrices
  vector<int> coordindex, freegens;
  scalar denom1,denom2,denom3;
  ssubspace kern; // kernel(delta) basis
  smat tkernbas; // transpose of kernel(delta) basis
  vector<modsym> freemods;
public:
  vector<svec> coord_vecs;
  mat coord;     // # cols = dimension
  mat projcoord; // # cols = # newforms after they are found
  long dimension, cuspidal_dimension;
public:
  // Constructor (does all the work):
  homspace(long n, // the level
           scalar mod, // the prime modulus for linear algebra
	   int hp, // plus-space flag (0 or 1 or -1)
	   int verbose // verbosity (0 : no output
	               //	     1 : basic short output
	               //            2 : lots of detail)
	   );
  long h1cuspdim() const {return cuspidal_dimension;}
  long h1dim() const {return dimension;}
  scalar h1denom() const {return denom1;}
  scalar h1cdenom() const {return denom3;}
  long h1ncusps() const {return ncusps;}
  vector<long> eigrange(long i);
  long op_prime(int i);  // the i'th operator prime for Tp or Wq
  mat opmat(int i, int dual, int verb=0);
  vec opmat_col(int i, int j, int verb=0);
  mat opmat_cols(int i, const vec_i& jlist, int verb=0);
  mat opmat_restricted(int i,const subspace& s, int dual, int verb=0);
  // versions returning an smat:
  smat s_opmat(int i,int dual,int verb=0);
  svec s_opmat_col(int i, int j, int verb=0);
  smat s_opmat_cols(int i, const vec_i& jlist, int verb=0);
  smat s_opmat_restricted(int i,const ssubspace& s, int dual,int verb=0);

  // Extend a dual vector of length dimension to one of length nsymb:
  vec extend_coords(const vec& v);
  // Contract a dual vector of length nsymb to one of length dimension:
  vec contract_coords(const vec& v);

public:
  // The next functions express M- & modular symbols in terms of the
  // basis for H_1(X_0(N);cusps;Z) of dimension dimension
  svec zero_coords() const {return svec(dimension);} // zero vector
  svec coords_from_index(int ind) const;
  vec proj_coords_from_index(int ind, const mat& m) const;
  scalar nfproj_coords_from_index(int ind, const vec& bas) const;
  svec coords(const symb& s) const;
  svec coords_cd(long c, long d) const;
  svec coords(long nn, long dd) const;
  svec coords(const rational& r) const  {return coords(num(r),den(r));}
  svec coords(const modsym& m) const;

  // versions which add to an existing svec:
  void add_coords(svec& v, const symb& s) const;
  void add_coords_cd(svec& v, long c, long d) const;
  void add_coords(svec& v, long nn, long dd) const;
  void add_coords(svec& v, const rational& r) const  {add_coords(v,num(r),den(r));}
  void add_coords(svec& v, const modsym& r) const;

  // The next functions express M- & modular symbols in terms of the
  // basis of a (dual) subspace of the whole space
  // The "projcoord" default (which requires the initialization of the
  // projcoord matrix as done in  newforms::createfromscratch()) which
  // has one column for each newform
  // NB these operate on vecs and not svecs
  // The "nf" versions give scalars and instead of a matrix take a
  // vector = unique column of a matrix, such as a newform's coordplus
  // vector.
  vec proj_coords_cd(long c, long d, const mat& m) const;
  vec proj_coords_cd(long c, long d) const
  {return proj_coords_cd(c,d,projcoord);}
  scalar nfproj_coords_cd(long c, long d, const vec& bas) const;
  void add_proj_coords_cd(vec& v, long c, long d, const mat& m) const;
  void add_proj_coords_cd(vec& v, long c, long d) const
  {add_proj_coords_cd(v,c,d,projcoord);}
  void add_nfproj_coords_cd(scalar& a, long c, long d, const vec& bas) const;

  vec proj_coords(long n, long d, const mat& bas) const;
  vec proj_coords(const rational& r, const mat& bas) const
  {return proj_coords(num(r), den(r), bas);}
  vec proj_coords(long n, long d) const
  {return proj_coords(n,d, projcoord);}
  vec proj_coords(const rational& r) const
  {return proj_coords(num(r), den(r), projcoord);}
  scalar nfproj_coords(long n, long d, const vec& bas) const;

  void add_proj_coords(vec& v, long n, long d, const mat& bas) const;
  void add_proj_coords(vec& v, const rational& r, const mat& bas) const
  {add_proj_coords(v, num(r), den(r), bas);}
  void add_proj_coords(vec& v, long n, long d) const
  {add_proj_coords(v,n,d,projcoord);}
  void add_nfproj_coords(scalar& aa, long n, long d, const vec& bas) const;

  vec cuspidalpart(const vec& v) const {return v[kern.pivs()];}

  svec applyop(const matop& T, const rational& q) const;
  svec applyop(const matop& T, const modsym& m) const;

  vec applyop_proj(const matop& T, const rational& q, const mat& bas) const;
  vec applyop_proj(const matop& T, const modsym& m, const mat& bas) const
  {return applyop_proj(T, m.beta(), bas) - applyop_proj(T, m.alpha(), bas);}
  vec applyop_proj(const matop& T, const rational& q) const
  {return applyop_proj(T, q, projcoord);}
  vec applyop_proj(const matop& T, const modsym& m) const
  {return applyop_proj(T, m, projcoord);};

  mat calcop(const matop& T, int cuspidal, int dual, int display=0) const;
  mat calcop(const gmatop& T, int cuspidal, int dual, int display=0) const;
  vec calcop_col(int j, const matop& T, int display=0) const;
  mat calcop_cols(const vec_i& jlist, const matop& T, int display=0) const;
  mat calcop_restricted(const matop& T, const subspace& s, int dual, int display=0) const;

  smat s_calcop(const matop& T, int cuspidal, int dual, int display=0) const;
  svec s_calcop_col(int j, const matop& T, int display=0) const;
  smat s_calcop_cols(const vec_i& jlist, const matop& T, int display=0) const;
  smat s_calcop_restricted(const matop& T, const ssubspace& s, int dual, int display=0) const;

  ZZX charpoly(const matop& T, int cuspidal) const;
  ZZX charpoly(const gmatop& T, int cuspidal) const;

  mat heckeop(long p, int cuspidal, int dual, int display=0) const
  {
    return calcop(matop(p,N), cuspidal, dual,display);
  }
  vec heckeop_col(long p, int j, int display=0) const
  {
    return calcop_col(j,matop(p,N),display);
  }
  mat heckeop_cols(long p, const vec_i& jlist, int display=0) const
  {
    return calcop_cols(jlist,matop(p,N),display);
  }
  mat heckeop_restricted(long p, const subspace& s, int dual, int display=0) const
  {
    return calcop_restricted(matop(p,N),s,dual,display);
  }
  smat s_heckeop(long p, int cuspidal, int dual, int display=0) const
  {
    return s_calcop(matop(p,N),cuspidal,dual,display);
  }
  svec s_heckeop_col(long p, int j, int display=0) const
  {
    return s_calcop_col(j,matop(p,N),display);
  }
  smat s_heckeop_cols(long p, const vec_i& jlist, int display=0) const
  {
    return s_calcop_cols(jlist,matop(p,N),display);
  }
  smat s_heckeop_restricted(long p, const ssubspace& s, int dual, int display=0) const
  {
    return s_calcop_restricted(matop(p,N),s,dual,display);
  }

  mat newheckeop(long p, int cuspidal, int dual, int display=0) const;
  mat wop(long q, int cuspidal, int dual, int display=0) const
  {
    return calcop(matop(q,N),cuspidal,dual,display);
  }
  smat s_wop(long q, int cuspidal, int dual, int display=0) const
  {
    return s_calcop(matop(q,N),cuspidal,dual,display);
  }
  mat fricke(int cuspidal, int dual, int display=0) const;
  mat conj(int cuspidal, int dual, int display=0) const;
  vec conj_col(int j, int display=0) const;
  mat conj_cols(const vec_i& jlist, int display=0) const;
  mat conj_restricted(const subspace& s, int dual,int display=0) const;
  smat s_conj(int cuspidal, int dual, int display=0) const;
  svec s_conj_col(int j, int display=0) const;
  smat s_conj_cols(const vec_i& jlist, int display=0) const;
  smat s_conj_restricted(const ssubspace& s, int dual, int display=0) const;
  vec maninvector(long p) const;
  vec projmaninvector(long p) const;
  vec projmaninvector(long p, const mat& m) const;
  vec manintwist(long p) const;
  vec newhecke(long p, long n, long d) const;
  //
  friend class jumps;
  friend class newforms;
};

// Return true iff T's new poly is squarefree and coprime to its old poly
int test_splitting_operator(const long& N, const gmatop& T, const scalar& mod, int verbose=0);

///////////////////////////////////////////////////////////////////////////////////////////
// functions for caching homspaces, full Hecke matrices, full and new Hecke polynomials
///////////////////////////////////////////////////////////////////////////////////////////

// Utilities for creating keys

string Nkey(const long& N);
string NPkey(const long& N, const long& p);
string NTkey(const long& N, const matop& T);
string NTkey(const long& N, const gmatop& T);

// Dicts (i.e. maps) holding various caches (declared in homspace.cc)
// Keys are strings encoding level, operator label created from above utilities

extern map<string,homspace*> H1_dict;  // Key: Nkey(N)
extern map<string, mat> full_mat_dict; // Key: Nkey(N)-T.name(). Value: matrix of T
                                       // on full space
extern map<string, ZZX> poly_dict;          // char polys on full space
extern map<string, ZZX> cuspidal_poly_dict; // char polys of restriction to cuspidal subspace
extern map<string, ZZX> new_poly_dict;      // char polys of restriction to new subspace
extern map<string, ZZX> new_cuspidal_poly_dict; // char polys of restriction to new cuspidal subspace

// Functions to retrieve a value from one of these dicts given its
// key, computing and storing it if the key is not already there:

// from H1_dict
homspace* get_homspace(const long& N, scalar mod);

// from full_mat_dict
mat get_full_mat(const long& N,  const matop& T, const scalar& mod);
mat get_full_mat(const long& N,  const gmatop& T, const scalar& mod);

// from one of poly_dict, cuspidal_poly_dict depending on cuspidal flag
ZZX get_poly(const long& N,  const gmatop& T, int cuspidal, const scalar& mod);
inline ZZX get_poly(const long& N,  const matop& T, int cuspidal, const scalar& mod)
{return get_poly(N, gmatop(T), cuspidal, mod);}

// from one of new_poly_dict, new_cuspidal_poly_dict depending on cuspidal flag

ZZX get_new_poly(const long& N, const gmatop& T, int cuspidal, const scalar& mod);
inline ZZX get_new_poly(const long& N, const matop& T, int cuspidal, const scalar& mod)
{return get_new_poly(N, gmatop(T), cuspidal, mod);}

// Functions to output and re-input poly dicts
void output_poly_dict(ostream& os, map<string, ZZX> D);
map<string, ZZX> input_poly_dict(istream& is);

#endif
