import _ from 'lodash' ; import { IParticle , IXYOperator , ITileIntensity } from './interfaces' ; import Vector from './Vector' ; import Operator from './Operator' ; import Dimension from './Dimension' ; import { polStates } from './Ops' ; import Complex , { Cx , ComplexFormat } from './Complex' ; import VectorEntry from './VectorEntry' ; import Measurement , { INamedVector , WeightedProjection } from './Measurement' ; export interface IXYNamedVectors { x : number ; y : number ; nVecs : INamedVector [ ] ; } export interface IXYNamedOperators { x : number ; y : number ; nOps : WeightedProjection [ ] ; } export interface IPhotonPolarization { x : number ; y : number ; direction : number ; h : Complex ; v : Complex ; } see {@link @Dimension.position} ,  {@link @Dimension.direction} ,  {@link @Dimension.polarization} * Designed so that it will work with https://github.com/Quantum-Game/quantum-game-2 board.
 *  todo todo export default class Photons { vector : Vector ; operators : IXYOperator [ ] ; cachedDiffU : Operator ; measurementVecs : INamedVector [ ] ; measurementOps : WeightedProjection [ ] ; readonly dimX : Dimension ; readonly dimY : Dimension ; param sizeX param sizeY param vector param operators constructor ( sizeX , sizeY , vector , operators = [ ] , measurementVecs = [ ] , measurementOps = [ ] ) { this . vector = vector ; this . operators = operators ; this . measurementVecs = measurementVecs ; this . measurementOps = measurementOps ; this . cachedDiffU = Photons . singlePhotonInteractionDiff ( sizeX , sizeY , operators ) ; this . dimX = Dimension . position ( sizeX , 'x' ) ; this . dimY = Dimension . position ( sizeY , 'y' ) ; } param operators updateOperators ( operators ) { this . operators = operators ; this . cachedDiffU = Photons . singlePhotonInteractionDiff ( this . sizeX , this . sizeY , operators ) ; } updateMeasurements ( xyVecs , xyOps = [ ] ) { this . measurementVecs = xyVecs . flatMap ( ( xyVec ) => xyVec . nVecs . map ( ( nVec ) => ( { name : [ `${ xyVec . x }-${ xyVec . y }-${ nVec . name [ 0 ] }` ] , vector : Photons . localizeVector ( this . sizeX , this . sizeY , xyVec . x , xyVec . y , nVec . vector ) , } ) ) ) ; this . measurementOps = xyOps . flatMap ( ( xyOp ) => xyOp . nOps . map ( ( nOp ) => WeightedProjection . new ( [ `${ xyOp . x }-${ xyOp . y }-${ nOp . name [ 0 ] }` ] , Photons . localizeOperator ( this . sizeX , this . sizeY , { x : xyOp . x , y : xyOp . y , op : nOp . operator } ) ) ) ) ; } param sizeX param sizeY static emptySpace ( sizeX , sizeY ) { const vector = new Vector ( [ ] , [ ] ) ; return new Photons ( sizeX , sizeY , vector , [ ] ) ; } /**
     * Size x ('width') of the board.
     */ get sizeX ( ) : number { return this . dimX . size ; } /**
     * Size y ('height') of the board.
     */ get sizeY ( ) : number { return this . dimY . size ; } /**
     * The total number of photons.
     */ get nPhotons ( ) : number { return this . vector . dimensions . length / 4 ; } returns todo copy ( ) { return new Photons ( this . sizeX , this . sizeY , this . vector . copy ( ) , this . operators ) ; } returns normalize ( ) { this . vector = this . vector . normalize ( ) ; return this ; } note param i returns vectorIndicesForParticle ( i ) { return [ 4 * i , 4 * i + 1 , 4 * i + 2 , 4 * i + 3 ] ; } note param i returns vectorPosDirIndicesForParticle ( i ) { return [ 4 * i , 4 * i + 1 , 4 * i + 2 ] ; } note param i returns vectorPosIndicesForParticle ( i ) { return [ 4 * i , 4 * i + 1 ] ; } todo param sizeX param sizeY param posX param posY param dirDirection param pol returns static vectorFromIndicator ( sizeX , sizeY , posX , posY , dir , pol ) { if ( polStates [ pol ] === undefined ) { throw new Error ( `Polarization string ${ pol } not supported.` ) ; } const dimensions = [ Dimension . position ( sizeX , 'x' ) , Dimension . position ( sizeY , 'y' ) , Dimension . direction ( ) ] ; const state = [ posX . toString ( ) , posY . toString ( ) , dir ] ; return Vector . indicator ( dimensions , state ) . outer ( polStates [ pol ] ) ; } param factor static allDirectionsVec ( factor = 1 ) { const dirs = [ '>' , '^' , '<' , 'v' ] ; const pols = [ 'H' , 'V' ] ; return dirs . flatMap ( ( dir ) => pols . map ( ( pol ) => ( { name : [ `${ dir }${ pol }` ] , vector : Vector . indicator ( [ Dimension . direction ( ) , Dimension . polarization ( ) ] , [ dir , pol ] ) . mulByReal ( factor ) , } ) ) ) ; } /**
     * Generates a DirPol projections for completem projective, non-destructive measurement.
     */ static allDirectionsOps ( ) { const dirs = [ '>' , '^' , '<' , 'v' ] ; const pols = [ 'H' , 'V' ] ; return dirs . flatMap ( ( dir ) => pols . map ( ( pol ) => WeightedProjection . new ( [ `${ dir }${ pol }` ] , Operator . indicator ( [ Dimension . direction ( ) , Dimension . polarization ( ) ] , [ dir , pol ] ) ) ) ) ; } /**
     * Add one more photon to the state, using  Photons . vectorFromIndicator .
     *
     *  param posX param posY param dir param pol returns addPhotonFromIndicator ( posX , posY , dir , pol ) { const newPhoton = Photons . vectorFromIndicator ( this . sizeX , this . sizeY , posX , posY , dir , pol ) ; const oldPhotons = this . vector ; if ( this . nPhotons === 0 ) { this . vector = newPhoton ; } else if ( this . nPhotons === 1 ) { if ( ! newPhoton . dot ( this . vector ) . isZero ) { throw ( `Adding photons not yet implemented for non-ortogonal states.` + `Old photon:\n${ this . vector }\nand new photon:\n${ newPhoton }` ) ; } this . vector = Vector . add ( [ oldPhotons . outer ( newPhoton ) , newPhoton . outer ( oldPhotons ) ] ) . mulByReal ( Math . SQRT1_2 ) ; } else { throw `Adding 3 or more particles not yet implemented. We already have: ${ this . nPhotons } photons.` ; } return this ; } param sizeX param sizeY param yDirMeansDown return An operator, with dimensions [dimX, dimY,  Dimension . direction ].
      static propagator ( sizeX , sizeY , yDirMeansDown = true ) { const dir = Dimension . direction ( ) ; const dimX = Dimension . position ( sizeX , 'x' ) ; const dimY = Dimension . position ( sizeY , 'y' ) ; const s = yDirMeansDown ? 1 : - 1 ; return Operator . add ( [ Operator . outer ( [ Operator . shift ( dimX , + 1 ) , Operator . identity ( [ dimY ] ) , Operator . indicator ( [ dir ] , [ '>' ] ) ] ) , Operator . outer ( [ Operator . shift ( dimX , - 1 ) , Operator . identity ( [ dimY ] ) , Operator . indicator ( [ dir ] , [ '<' ] ) ] ) , Operator . outer ( [ Operator . identity ( [ dimX ] ) , Operator . shift ( dimY , + s ) , Operator . indicator ( [ dir ] , [ 'v' ] ) ] ) , Operator . outer ( [ Operator . identity ( [ dimX ] ) , Operator . shift ( dimY , - s ) , Operator . indicator ( [ dir ] , [ '^' ] ) ] ) , ] ) ; } /**
     * Propagate all particles, using  createPhotonPropagator .
     * Use it for a reference. All practical operations with  propagatePhotons .
     *  param yDirMeansDown returns propagatePhotonsWithOperator ( yDirMeansDown = true ) { const photonPropagator = Photons . propagator ( this . sizeX , this . sizeY , yDirMeansDown ) ; _ . range ( this . nPhotons ) . forEach ( ( i ) => { this . vector = photonPropagator . mulVecPartial ( this . vectorPosDirIndicesForParticle ( i ) , this . vector ) ; } ) ; return this ; } /**
     * Propagate all particles, hardcoded.
     * See  propagatePhotonsWithOperator for a reference.
     *
     *  returns propagatePhotons ( ) { const dirToShiftX = ( dir ) : number => { if ( dir === 0 ) { return 1 ; } else if ( dir === 2 ) { return - 1 ; } else { return 0 ; } } ; const dirToShiftY = ( dir ) : number => { if ( dir === 1 ) { return - 1 ; } else if ( dir === 3 ) { return 1 ; } else { return 0 ; } } ; const { sizeX , sizeY , nPhotons } = this ; let newEntries = [ ... this . vector . entries ] ; for ( let i = 0 ; i < nPhotons ; i ++ ) { const [ iX , iY , iDir ] = this . vectorPosDirIndicesForParticle ( i ) ; newEntries = newEntries . map ( ( entry ) => { const dir = entry . coord [ iDir ] ; const x = entry . coord [ iX ] + dirToShiftX ( dir ) ; const y = entry . coord [ iY ] + dirToShiftY ( dir ) ; const newCoords = entry . coord . slice ( ) ; newCoords [ iX ] = x ; newCoords [ iY ] = y ; return new VectorEntry ( newCoords , entry . value ) ; } ) . filter ( ( entry ) => { const x = entry . coord [ iX ] ; const y = entry . coord [ iY ] ; return 0 <= x && x < sizeX && 0 <= y && y < sizeY ; } ) ; } this . vector = new Vector ( newEntries , this . vector . dimensions ) ; return this ; } see localizeOperator param sizeX param sizeY param x param y param vec static localizeVector ( sizeX , sizeY , x , y , vec ) { const dimX = Dimension . position ( sizeX , 'x' ) ; const dimY = Dimension . position ( sizeY , 'y' ) ; return Vector . indicator ( [ dimX , dimY ] , [ `${ x }` , `${ y }` ] ) . outer ( vec ) ; } param sizeX param sizeY param posX param posY param op returns static localizeOperator ( sizeX , sizeY , op ) { const dimX = Dimension . position ( sizeX , 'x' ) ; const dimY = Dimension . position ( sizeY , 'y' ) ; return Operator . outer ( [ Operator . indicator ( [ dimX , dimY ] , [ `${ op . x }` , `${ op . y }` ] ) , op . op ] ) ; } /**
     * Perform measurement given measurement projections and positive operators.
     */ measure ( ) { let ensamble = Measurement . fromVector ( this . vector ) ; for ( let i = this . nPhotons - 1 ; i >= 0 ; i -- ) { ensamble = ensamble . projectiveMeasurement ( this . vectorIndicesForParticle ( 0 ) , this . measurementVecs , this . measurementOps ) ; } return ensamble ; } deprecated param posX param posY param op returns measureAbsorptionAtOperator ( op , photonId = 0 ) { const localizedOperator = Photons . localizeOperator ( this . dimX . size , this . dimY . size , op ) ; const localizedId = Operator . indicator ( [ this . dimX , this . dimY ] , [ `${ op . x }` , `${ op . y }` ] ) ; const newVector = localizedOperator . mulVecPartial ( this . vectorIndicesForParticle ( photonId ) , this . vector ) ; const oldVector = localizedId . mulVecPartial ( this . vectorPosIndicesForParticle ( photonId ) , this . vector ) ; return oldVector . normSquared ( ) - newVector . normSquared ( ) ; } deprecated Demo of measurement of one particle
     * So far the basis is FIXED, so it won't give corrent results with operators absorbing in a basis
     * that does not commute with this basis.
     * Vide  measureAbsorptionAtOperator as the structure is
     *  param posX param posY param op param photonId return vectorValuedMeasurement ( op , photonId = 0 ) { const localizedOperator = Photons . localizeOperator ( this . sizeX , this . sizeY , op ) ; const basis = [ '>H' , '>V' , '^H' , '^V' , '<H' , '<V' , 'vH' , 'vV' ] ; const dimDir = Dimension . direction ( ) ; const dimPol = Dimension . polarization ( ) ; const posInd = this . vectorPosIndicesForParticle ( photonId ) ; const dirPolInd = [ 4 * photonId + 2 , 4 * photonId + 3 ] ; const localizedId = Operator . indicator ( [ this . dimX , this . dimY ] , [ `${ op . x }` , `${ op . y }` ] ) ; const oldVectorHere = localizedId . mulVecPartial ( [ ... posInd ] , this . vector ) ; return basis . map ( ( coordStr ) => { const projection = Operator . indicator ( [ dimDir , dimPol ] , coordStr ) ; const vectorProjected = projection . mulVecPartial ( dirPolInd , oldVectorHere ) ; const inputProjectedProbabability = vectorProjected . normSquared ( ) ; const allId = this . vectorIndicesForParticle ( photonId ) ; const outputProjectedProbability = localizedOperator . mulVecPartial ( [ ... allId ] , vectorProjected ) . normSquared ( ) ; const p = inputProjectedProbabability - outputProjectedProbability ; const proj = Vector . indicator ( [ this . dimX , this . dimY , dimDir , dimPol ] , [ `${ op . x }` , `${ op . y }` , coordStr [ 0 ] , coordStr [ 1 ] ] ) ; const newPhotons = this . copy ( ) ; newPhotons . vector = proj . innerPartial ( [ ... allId ] , this . vector ) ; return { photonId : photonId , x : op . x , y : op . y , dirStr : coordStr [ 0 ] , polStr : coordStr [ 1 ] , inputProb : inputProjectedProbabability , probability : p , projectedState : newPhotons . ketString ( ) , } ; } ) . filter ( ( d ) => d . inputProb > 0 ) ; } param sizeX param sizeY param opsWithPos static singlePhotonInteractionDiff ( sizeX , sizeY , opsWithPos ) { const localizedOpsShifted = opsWithPos . map ( ( d ) => { const { x , y , op } = d ; const idDirPol = Operator . identity ( [ Dimension . direction ( ) , Dimension . polarization ( ) ] ) ; const shiftedOp = op . sub ( idDirPol ) ; return Photons . localizeOperator ( sizeX , sizeY , { x , y , op : shiftedOp } ) ; } ) ; if ( localizedOpsShifted . length === 0 ) { localizedOpsShifted . push ( Operator . zeros ( [ Dimension . position ( sizeX , 'x' ) , Dimension . position ( sizeY , 'y' ) , Dimension . direction ( ) , Dimension . polarization ( ) , ] ) ) ; } return Operator . add ( localizedOpsShifted ) ; } remark Some space for improvement with avoiding identity (direct sum structure),
     * vide  Operator . mulVecPartial .
     *  param sizeX param sizeY param opsWithPos static singlePhotonInteraction ( sizeX , sizeY , opsWithPos ) { const localizedOpsShifted = Photons . singlePhotonInteractionDiff ( sizeX , sizeY , opsWithPos ) ; const dimX = Dimension . position ( sizeX , 'x' ) ; const dimY = Dimension . position ( sizeY , 'y' ) ; return localizedOpsShifted . add ( Operator . identity ( [ dimX , dimY , Dimension . direction ( ) , Dimension . polarization ( ) ] ) ) ; } remark returns actOnSinglePhotons ( ) { _ . range ( this . nPhotons ) . forEach ( ( i ) => { this . vector = this . cachedDiffU . mulVecPartial ( this . vectorIndicesForParticle ( i ) , this . vector ) . add ( this . vector ) ; } ) ; return this ; } deprecated returns todo aggregatePolarization ( ) { if ( this . nPhotons !== 1 ) { throw `Right now implemented only for 1 photon. Here we have ${ this . nPhotons } photons.` ; } return Photons . onePhotonByPolarization ( this . vector ) . map ( ( { x , y , direction , h , v } ) => ( { x , y , direction , are : h . re , aim : h . im , bre : v . re , bim : v . im , } ) ) ; } param v static onePhotonByPolarization ( v ) { if ( v . dimensions . length !== 4 ) { throw `Right now implemented only for 1 photon, 4 dimensions. Here we have ${ v . dimensions . length } dimensions.` ; } return _ . chain ( v . entries ) . groupBy ( ( entry ) => _ . at ( entry . coord , [ 0 , 1 , 2 ] ) ) . values ( ) . map ( ( entries ) => { const first = entries [ 0 ] ; const [ x , y , dir , _pol ] = first . coord ; const amplitudes = [ Cx ( 0 ) , Cx ( 0 ) ] ; entries . forEach ( ( entry ) => { amplitudes [ entry . coord [ 3 ] ] = entry . value ; } ) ; const [ h , v ] = amplitudes ; return { x , y , direction : 90 * dir , h , v , } ; } ) . value ( ) ; } /**
     * Sampling states for (possibly entangled) particles.
     * A proof-of-concept, everything may change (including its mathematics).
     * For product (i.e. pure, non-entangled) states the result is deterministic, up to the phase.
     */ sampleTwoPhotonState ( ) { if ( this . nPhotons !== 2 ) { throw `Requires exactly 2 photons. Here we have ${ this . nPhotons } photons.` ; } const coordA = [ 0 , 1 , 2 , 3 ] ; const coordB = [ 4 , 5 , 6 , 7 ] ; const randA = this . vector . randomOnPartialSubspace ( coordA ) ; const resB = randA . innerPartial ( coordA , this . vector ) . normalize ( ) ; const resA = resB . innerPartial ( coordB , this . vector ) . normalize ( ) ; return [ Photons . onePhotonByPolarization ( resA ) , Photons . onePhotonByPolarization ( resB ) ] ; } todo totalIntensityPerTile ( ) { if ( this . nPhotons !== 1 ) { throw `Right now implemented only for 1 photon. Here we have ${ this . nPhotons } photons.` ; } const aggregated = _ . chain ( this . vector . entries ) . groupBy ( ( entry ) => _ . at ( entry . coord , [ 0 , 1 ] ) ) . values ( ) . map ( ( entries ) => { const first = entries [ 0 ] ; const [ x , y , _dir , _pol ] = first . coord ; const probability = entries . map ( ( entry ) => entry . value . abs2 ( ) ) . reduce ( ( a , b ) => a + b ) ; return { x , y , probability } ; } ) . value ( ) ; return aggregated ; } /**
     * Generates a string for kets.
     * See  Vector . toString for formatting options.
     *  param complexFormat param precision returns ketString ( complexFormat = 'cartesian' , precision = 2 ) { return this . vector . toString ( complexFormat , precision , ' + ' , false ) ; } }