import _ , { entries } from 'lodash' ; import { coordsToIndex , checkCoordsSizesCompability , isPermutation , indicesComplement , joinCoordsFunc } from './helpers' ; import Complex , { Cx , ComplexFormat } from './Complex' ; import VectorEntry from './VectorEntry' ; import OperatorEntry from './OperatorEntry' ; import Vector from './Vector' ; import Dimension from './Dimension' ; import Basis from './Basis' ; import { IColumnOrRow , IEntryIndexIndexValue } from './interfaces' ; /**
 * Operator class.
 * A complex number sparse matrix aware of dimensions and tensor structure.
 */ export default class Operator { readonly entries : OperatorEntry [ ] ; readonly dimensionsOut : Dimension [ ] ; readonly dimensionsIn : Dimension [ ] ; private cachedPerOutput : IColumnOrRow [ ] | null = null ; private cachedPerInput : IColumnOrRow [ ] | null = null ; /**
     * Creates an operator from sparse entires.
     * This is a low-level method (due to the explicit use of  OperatorEntry ).
     * You may need  Operator . fromArray or  Operator . fromSparseCoordNames instead.
     *  param entries param dimensionsOut param dimensionsIn constructor ( entries , dimensionsOut , dimensionsIn = dimensionsOut ) { this . entries = entries ; this . dimensionsOut = dimensionsOut ; this . dimensionsIn = dimensionsIn ; this . entries . forEach ( ( entry ) => { checkCoordsSizesCompability ( entry . coordOut , this . sizeOut ) ; checkCoordsSizesCompability ( entry . coordIn , this . sizeIn ) ; } ) ; } returns get dimensions ( ) : [ Dimension [ ] , Dimension [ ] ] { return [ this . dimensionsOut , this . dimensionsIn ] ; } returns see Dimension get sizeOut ( ) : number [ ] { return this . dimensionsOut . map ( ( dimension ) => dimension . size ) ; } returns see Dimension get sizeIn ( ) : number [ ] { return this . dimensionsIn . map ( ( dimension ) => dimension . size ) ; } returns get totalSizeOut ( ) : number { return this . sizeOut . reduce ( ( a , b ) => a * b ) ; } returns get totalSizeIn ( ) : number { return this . sizeIn . reduce ( ( a , b ) => a * b ) ; } returns see Dimension get namesOut ( ) : string [ ] { return this . dimensionsOut . map ( ( dimension ) => dimension . name ) ; } returns see Dimension get namesIn ( ) : string [ ] { return this . dimensionsIn . map ( ( dimension ) => dimension . name ) ; } returns see Dimension get coordNamesOut ( ) : string [ ] [ ] { return this . dimensionsOut . map ( ( dimension ) => dimension . coordNames ) ; } returns see Dimension get coordNamesIn ( ) : string [ ] [ ] { return this . dimensionsIn . map ( ( dimension ) => dimension . coordNames ) ; } returns conj ( ) { const entries = this . entries . map ( ( entry ) => new OperatorEntry ( [ ... entry . coordOut ] , [ ... entry . coordIn ] , entry . value . conj ( ) ) ) ; return new Operator ( entries , this . dimensionsOut , this . dimensionsIn ) ; } returns transpose ( ) { const entries = this . entries . map ( ( entry ) => new OperatorEntry ( [ ... entry . coordIn ] , [ ... entry . coordOut ] , entry . value ) ) ; return new Operator ( entries , this . dimensionsIn , this . dimensionsOut ) ; } returns dag ( ) { const entries = this . entries . map ( ( entry ) => new OperatorEntry ( [ ... entry . coordIn ] , [ ... entry . coordOut ] , entry . value . conj ( ) ) ) ; return new Operator ( entries , this . dimensionsIn , this . dimensionsOut ) ; } param m2 returns todo outer ( m2 ) { const m1 = this ; const dimensionsOut = m1 . dimensionsOut . concat ( m2 . dimensionsOut ) ; const dimensionsIn = m1 . dimensionsIn . concat ( m2 . dimensionsIn ) ; const entries = [ ] ; m1 . entries . flatMap ( ( entry1 ) => m2 . entries . map ( ( entry2 ) => entries . push ( entry1 . outer ( entry2 ) ) ) ) ; return new Operator ( entries , dimensionsOut , dimensionsIn ) ; } param m2 returns see Operator . add for the actual implementation.
      add ( m2 ) { return Operator . add ( [ this , m2 ] ) ; } param c returns mulConstant ( c ) { const entries = this . entries . map ( ( entry ) => new OperatorEntry ( entry . coordOut , entry . coordIn , entry . value . mul ( c ) ) ) ; return new Operator ( entries , this . dimensionsOut , this . dimensionsIn ) ; } param x returns mulByReal ( x ) { return this . mulConstant ( Cx ( x ) ) ; } param m2 returns sub ( m2 ) { return this . add ( m2 . mulConstant ( Cx ( - 1 ) ) ) ; } /**
     * Map values
     */ mapValues ( func ) { const entries = this . entries . map ( ( entry ) => new OperatorEntry ( entry . coordOut , entry . coordIn , func ( entry . value ) ) ) ; return new Operator ( entries , this . dimensionsOut , this . dimensionsIn ) ; } returns toVectorPerOutput ( ) { if ( this . cachedPerOutput == null ) { this . cachedPerOutput = _ ( this . entries ) . groupBy ( ( entry ) => entry . coordOut . join ( '|' ) ) . map ( ( entries ) => { const vecEntries = entries . map ( ( opEntry ) => new VectorEntry ( opEntry . coordIn , opEntry . value ) ) ; return { coord : entries [ 0 ] . coordOut , vector : new Vector ( vecEntries , this . dimensionsIn ) } ; } ) . value ( ) ; } return this . cachedPerOutput ; } returns toVectorPerInput ( ) { if ( this . cachedPerInput == null ) { this . cachedPerInput = _ ( this . entries ) . groupBy ( ( entry ) => entry . coordIn . join ( '|' ) ) . values ( ) . map ( ( entries ) => { const vecEntries = entries . map ( ( opEntry ) => new VectorEntry ( opEntry . coordOut , opEntry . value ) ) ; return { coord : entries [ 0 ] . coordIn , vector : new Vector ( vecEntries , this . dimensionsOut ) } ; } ) . value ( ) ; } return this . cachedPerInput ; } param v returns mulVec ( v ) { Dimension . checkDimensions ( this . dimensionsIn , v . dimensions ) ; const vecEntries = this . toVectorPerOutput ( ) . map ( ( row ) => new VectorEntry ( row . coord , row . vector . dot ( v ) ) ) . filter ( ( entry ) => ! entry . value . isAlmostZero ( ) ) ; return new Vector ( vecEntries , [ ... this . dimensionsOut ] ) ; } param m2 returns mulOp ( m2 ) { const m1 = this ; Dimension . checkDimensions ( m1 . dimensionsIn , m2 . dimensionsOut ) ; const vecPerInput = m2 . toVectorPerInput ( ) ; const entries = m1 . toVectorPerOutput ( ) . flatMap ( ( row ) => vecPerInput . map ( ( col ) => new OperatorEntry ( row . coord , col . coord , row . vector . dot ( col . vector ) ) ) ) . filter ( ( entry ) => ! entry . value . isAlmostZero ( ) ) ; return new Operator ( entries , [ ... m1 . dimensionsOut ] , [ ... m2 . dimensionsIn ] ) ; } param coordIndices param v returns mulVecPartial ( coordIndices , v ) { const complementIndices = indicesComplement ( coordIndices , v . dimensions . length ) ; const joinCoords = joinCoordsFunc ( coordIndices , complementIndices ) ; const newEntries = v . toGroupedByCoords ( coordIndices ) . map ( ( row ) => ( { coord : row . coord , vector : this . mulVec ( row . vector ) , } ) ) . flatMap ( ( row ) => row . vector . entries . map ( ( entry ) => new VectorEntry ( joinCoords ( row . coord , entry . coord ) , entry . value ) ) ) ; return new Vector ( newEntries , v . dimensions ) ; } param coordIndices param v returns contractLeft ( coordIndices , v ) { const complementIndices = indicesComplement ( coordIndices , this . dimensionsOut . length ) ; const newEntries = this . toVectorPerInput ( ) . map ( ( col ) => ( { coord : col . coord , vector : v . dotPartial ( coordIndices , col . vector ) , } ) ) . flatMap ( ( col ) => col . vector . entries . map ( ( entry ) => new OperatorEntry ( entry . coord , col . coord , entry . value ) ) ) ; return new Operator ( newEntries , _ . at ( this . dimensionsOut , complementIndices ) , [ ... this . dimensionsIn ] ) ; } param coordIndices param v returns contractRight ( coordIndices , v ) { const complementIndices = indicesComplement ( coordIndices , this . dimensionsIn . length ) ; const newEntries = this . toVectorPerOutput ( ) . map ( ( row ) => ( { coord : row . coord , vector : v . dotPartial ( coordIndices , row . vector ) , } ) ) . flatMap ( ( row ) => row . vector . entries . map ( ( entry ) => new OperatorEntry ( row . coord , entry . coord , entry . value ) ) ) ; return new Operator ( newEntries , [ ... this . dimensionsOut ] , _ . at ( this . dimensionsIn , complementIndices ) ) ; } returns see https trace ( ) { Dimension . checkDimensions ( this . dimensionsIn , this . dimensionsOut ) ; return this . entries . filter ( ( entry ) => entry . coordOut . toString ( ) === entry . coordIn . toString ( ) ) . map ( ( entry ) => entry . value ) . reduce ( ( a , b ) => a . add ( b ) , Cx ( 0 ) ) ; } param coordIndices returns partialTrace ( coordIndices ) { Dimension . checkDimensions ( this . dimensionsIn , this . dimensionsOut ) ; const complementIndices = indicesComplement ( coordIndices , this . dimensionsIn . length ) ; const takeI = ( coords ) : number [ ] => _ . at ( coords , coordIndices ) ; const takeCI = ( coords ) : number [ ] => _ . at ( coords , complementIndices ) ; const newEntries = _ ( this . entries ) . filter ( ( entry ) => takeI ( entry . coordOut ) . toString ( ) === takeI ( entry . coordIn ) . toString ( ) ) . map ( ( entry ) : [ number [ ] , number [ ] , Complex ] => [ takeCI ( entry . coordOut ) , takeCI ( entry . coordIn ) , entry . value ] ) . groupBy ( ( [ coordOut , coordIn , _value ] ) => `${ coordOut . join ( '|' ) },${ coordIn . join ( '|' ) }` ) . values ( ) . map ( ( entries ) => { const [ coordOut , coordIn ] = entries [ 0 ] ; const total = entries . map ( ( [ _coordOut , _coordIn , val ] ) => val ) . reduce ( ( a , b ) => a . add ( b ) , Cx ( 0 ) ) ; return new OperatorEntry ( coordOut , coordIn , total ) ; } ) . value ( ) ; return new Operator ( newEntries , _ . at ( this . dimensionsOut , complementIndices ) ) ; } param orderOut param orderIn permute ( orderOut , orderIn = orderOut ) { if ( ! isPermutation ( orderOut , this . dimensionsOut . length ) ) { throw new Error ( `${ orderOut } is not a valid permutation for ${ this . dimensionsOut . length } output dimensions.` ) ; } if ( ! isPermutation ( orderIn , this . dimensionsIn . length ) ) { throw new Error ( `${ orderIn } is not a valid permutation for ${ this . dimensionsIn . length } input dimensions.` ) ; } const dimensionsOut = _ . at ( this . dimensionsOut , orderOut ) ; const dimensionsIn = _ . at ( this . dimensionsIn , orderIn ) ; const entries = this . entries . map ( ( entry ) => new OperatorEntry ( _ . at ( entry . coordOut , orderOut ) , _ . at ( entry . coordIn , orderIn ) , entry . value ) ) ; return new Operator ( entries , dimensionsOut , dimensionsIn ) ; } param orderOut permuteDimsOut ( orderOut ) { if ( ! isPermutation ( orderOut , this . dimensionsOut . length ) ) { throw new Error ( `${ orderOut } is not a valid permutation for ${ this . dimensionsOut . length } output dimensions.` ) ; } const dimensionsOut = _ . at ( this . dimensionsOut , orderOut ) ; const entries = this . entries . map ( ( entry ) => new OperatorEntry ( _ . at ( entry . coordOut , orderOut ) , entry . coordIn , entry . value ) ) ; return new Operator ( entries , dimensionsOut , this . dimensionsIn ) ; } param orderIn permuteDimsIn ( orderIn ) { if ( ! isPermutation ( orderIn , this . dimensionsIn . length ) ) { throw new Error ( `${ orderIn } is not a valid permutation for ${ this . dimensionsIn . length } input dimensions.` ) ; } const dimensionsIn = _ . at ( this . dimensionsIn , orderIn ) ; const entries = this . entries . map ( ( entry ) => new OperatorEntry ( entry . coordOut , _ . at ( entry . coordIn , orderIn ) , entry . value ) ) ; return new Operator ( entries , this . dimensionsOut , dimensionsIn ) ; } see Basis . fromString and  changeAllDimsOfVector param dimName param basisStr toBasisAll ( dimName , basisStr ) { const basis = Basis . fromString ( dimName , basisStr ) ; return basis . changeAllDimsOfOperator ( this ) ; } see Complex . toString for formating options.
     *
     *  param complexFormat param precision param separator param intro returns toString ( complexFormat = 'cartesian' , precision = 2 , separator = ' + ' , intro = true ) { const valueStr = this . entries . map ( ( entry ) => { const coordStrOut = entry . coordOut . map ( ( i , dim ) => this . coordNamesOut [ dim ] [ i ] ) ; const coordStrIn = entry . coordIn . map ( ( i , dim ) => this . coordNamesIn [ dim ] [ i ] ) ; return `${ entry . value . toString ( complexFormat , precision ) } |${ coordStrOut }⟩⟨${ coordStrIn }|` ; } ) . join ( separator ) ; if ( intro ) { const introStr = `Operator with ${ this . entries . length } entries ` + `of max size [[${ this . sizeOut }], [${ this . sizeIn }]] ` + `with dimensions [[${ this . namesOut }], [${ this . namesIn }]]` ; return `${ introStr }\n${ valueStr }\n` ; } else { return valueStr ; } } returns toDense ( ) { const denseVector = _ . range ( this . totalSizeOut ) . map ( ( ) => _ . range ( this . totalSizeIn ) . map ( ( ) => Cx ( 0 ) ) ) ; this . entries . forEach ( ( entry ) => { const i = coordsToIndex ( entry . coordOut , this . sizeOut ) ; const j = coordsToIndex ( entry . coordIn , this . sizeIn ) ; denseVector [ i ] [ j ] = entry . value ; } ) ; return denseVector ; } returns toIndexIndexValues ( ) { return this . entries . map ( ( entry ) => ( { i : coordsToIndex ( entry . coordOut , this . sizeOut ) , j : coordsToIndex ( entry . coordIn , this . sizeIn ) , v : entry . value , } ) ) ; } param dimensions returns static identity ( dimensions ) { const sizes = dimensions . map ( ( dimension ) => dimension . size ) ; const totalSize = sizes . reduce ( ( a , b ) => a * b ) ; const entries = _ . range ( totalSize ) . map ( ( index ) => OperatorEntry . fromIndexIndexValue ( index , index , sizes , sizes , Cx ( 1 , 0 ) ) ) ; return new Operator ( entries , dimensions , dimensions ) ; } param dimension param shift static shift ( dimension , shift ) { const start = Math . max ( 0 , - shift ) ; const end = Math . min ( dimension . size , dimension . size - shift ) ; const entries = _ . range ( start , end ) . map ( ( index ) => OperatorEntry . fromIndexIndexValue ( index + shift , index , [ dimension . size ] , [ dimension . size ] , Cx ( 1 , 0 ) ) ) ; return new Operator ( entries , [ dimension ] , [ dimension ] ) ; } param dimensionsOut param dimensionsIn returns static zeros ( dimensionsOut , dimensionsIn = dimensionsOut ) { return new Operator ( [ ] , dimensionsOut , dimensionsIn ) ; } example param denseArray param dimensionsOut param dimensionsIn param removeZeros todo static fromArray ( denseArray , dimensionsOut , dimensionsIn = dimensionsOut , removeZeros = true ) { const sizesOut = dimensionsOut . map ( ( dimension ) => dimension . size ) ; const totalSizeOut = sizesOut . reduce ( ( a , b ) => a * b ) ; const sizesIn = dimensionsIn . map ( ( dimension ) => dimension . size ) ; const totalSizeIn = sizesIn . reduce ( ( a , b ) => a * b ) ; const rowLengths = denseArray . map ( ( row ) => row . length ) ; if ( _ . min ( rowLengths ) !== _ . max ( rowLengths ) ) { throw new Error ( `Is not a rectangular array. Row sizes ${ _ . min ( rowLengths ) } to ${ _ . max ( rowLengths ) }.` ) ; } if ( denseArray . length !== totalSizeOut || denseArray [ 0 ] . length !== totalSizeIn ) { throw new Error ( `Dimension inconsistency: array is [${ denseArray . length }, ${ denseArray [ 0 ] . length }] ` + `and dimensions total sizes are [${ totalSizeOut }, ${ totalSizeIn }]` ) ; } const flatlist = [ ] ; denseArray . forEach ( ( row , indexOut ) => row . forEach ( ( value , indexIn ) => flatlist . push ( [ indexOut , indexIn , value ] ) ) ) ; const entries = flatlist . filter ( ( [ _indexOut , _indexIn , value ] ) : boolean => ! removeZeros || ! value . isZero ( ) ) . map ( ( [ indexOut , indexIn , value ] ) : OperatorEntry => OperatorEntry . fromIndexIndexValue ( indexOut , indexIn , sizesOut , sizesIn , value ) ) ; return new Operator ( entries , dimensionsOut , dimensionsIn ) ; } example param dimensions param coordNames static indicator ( dimensions , coordNames ) { const coords = Dimension . stringToCoordIndices ( coordNames , dimensions ) ; const entries = [ new OperatorEntry ( coords , coords , Cx ( 1 ) ) ] ; return new Operator ( entries , dimensions , dimensions ) ; } example param stringedEntries param dimensionsOut param dimensionsIn returns static fromSparseCoordNames ( stringedEntries , dimensionsOut , dimensionsIn = dimensionsOut ) { const entries = stringedEntries . map ( ( [ coordNameStrOut , coordNameStrIn , value ] ) => new OperatorEntry ( Dimension . stringToCoordIndices ( coordNameStrOut , dimensionsOut ) , Dimension . stringToCoordIndices ( coordNameStrIn , dimensionsIn ) , value ) ) ; return new Operator ( entries , dimensionsOut , dimensionsIn ) ; } param v returns static projectionOn ( v ) { const opEntries = v . entries . flatMap ( ( entryOut ) => v . entries . map ( ( entryIn ) => new OperatorEntry ( entryOut . coord , entryIn . coord , entryOut . value . mul ( entryIn . value . conj ( ) ) ) ) ) ; return new Operator ( opEntries , [ ... v . dimensions ] ) ; } /**
     * L2 norm, Frobenius norm, or Hilbert-Schmidt norm. Squared.
     * https://en.wikipedia.org/wiki/Matrix_norm#Frobenius_norm
     */ normSquared ( ) { return this . entries . map ( ( entry ) => entry . value . abs2 ( ) ) . reduce ( ( a , b ) => a + b , 0 ) ; } param eps return isCloseToZero ( eps = 1e-6 ) { return Math . sqrt ( this . normSquared ( ) ) < eps ; } param m2 param eps return isCloseTo ( m2 , eps = 1e-6 ) { return this . sub ( m2 ) . isCloseToZero ( eps ) ; } see https param eps return isCloseToHermitian ( eps = 1e-6 ) { return this . dag ( ) . isCloseTo ( this , eps ) ; } note param eps return isCloseToIdentity ( eps = 1e-6 ) { Dimension . checkDimensions ( this . dimensionsIn , this . dimensionsOut ) ; const idOp = Operator . identity ( [ ... this . dimensionsIn ] ) ; return this . isCloseTo ( idOp , eps ) ; } see https param eps return isCloseToProjection ( eps = 1e-6 ) { return this . mulOp ( this ) . isCloseTo ( this , eps ) ; } see https param eps return isCloseToUnitary ( eps = 1e-6 ) { return this . dag ( ) . mulOp ( this ) . isCloseToIdentity ( eps ) ; } see https param eps return isCloseToNormal ( eps = 1e-6 ) { const lhs = this . dag ( ) . mulOp ( this ) ; const rhs = this . mulOp ( this . dag ( ) ) ; return lhs . isCloseTo ( rhs , eps ) ; } param eps returns isCloseToUnitaryOnSubspace ( eps = 1e-6 ) { const proj = this . dag ( ) . mulOp ( this ) ; return this . isCloseToNormal ( eps ) && proj . isCloseToProjection ( eps ) ; } see Operator . outer for the actual implementation.
     *
     *  param ops returns todo static outer ( ops ) { return ops . reduce ( ( acc , x ) => acc . outer ( x ) ) ; } param ops returns static add ( ops ) { if ( ops . length === 0 ) { throw new Error ( 'add requires at least one operator' ) ; } if ( ops . length === 1 ) { return ops [ 0 ] ; } const [ m1 , ... rest ] = ops ; for ( const m2 of rest ) { Dimension . checkDimensions ( m1 . dimensionsIn , m2 . dimensionsIn ) ; Dimension . checkDimensions ( m1 . dimensionsOut , m2 . dimensionsOut ) ; } const entriesByCoord = { } ; for ( const entry of m1 . entries ) { entriesByCoord [ entry . coordKey ( ) ] = entry ; } for ( const m2 of rest ) { for ( const entry of m2 . entries ) { const key = entry . coordKey ( ) ; if ( entriesByCoord . hasOwnProperty ( key ) ) { const value = entriesByCoord [ key ] . value . add ( entry . value ) ; if ( value . isAlmostZero ( ) ) { delete entriesByCoord [ key ] ; } else { entriesByCoord [ key ] = new OperatorEntry ( entry . coordOut , entry . coordIn , value ) ; } } else { entriesByCoord [ key ] = entry ; } } } return new Operator ( Object . values ( entriesByCoord ) , m1 . dimensionsOut , m1 . dimensionsIn ) ; } }