@@ -420,7 +420,7 @@ def generate_pdf_model(
420420 [ list of a separate architecture per replica ]
421421 which can be, but is not necessary, equal for all replicas
422422
423- <preprocessing factors>
423+ [ <preprocessing factors> ]
424424 postprocessing of the network output by a variation x^{alpha}*(1-x)^{beta}
425425
426426 <normalization>
@@ -474,29 +474,18 @@ def generate_pdf_model(
474474 Function to apply to the input. If given the input to the model
475475 will be a (1, None, 2) tensor where dim [:,:,0] is scaled
476476 When None, instead turn the x point into a (x, log(x)) pair
477- num_replicas: int
478- How many models should be trained in parallel.
479477 photons: :py:class:`validphys.photon.compute.Photon`
480478 If given, gives the AddPhoton layer a function to compute a photon which will be added at the
481479 index 0 of the 14-size FK basis
482480 This same function will also be used to compute the MSR component for the photon
483481
484-
485-
486482 Returns
487483 -------
488484 pdf_model: MetaModel
489485 pdf model, with `single_replica_generator` attached as an attribute
490486 """
491- if len (seed_list ) != num_replicas :
492- # TODO: remove this error, remove the num_replicas argument
493- raise ValueError ("This should not happen" )
494-
495- num_replicas = len (seed_list )
496-
497487 # Separate the settings which may be different for each replica
498488 # from those that are guaranteed to be equal for all replicas
499-
500489 all_replicas = []
501490 for seed in seed_list :
502491 tmp = _ReplicaSettings (
@@ -649,44 +638,44 @@ def _pdfNN_layer_generator(
649638 pdf_model: n3fit.backends.MetaModel
650639 a model f(x) = y where x is a tensor (1, xgrid, 1) and y a tensor (1, replicas, xgrid, out)
651640 """
652- # TODO: at the moment nothing changes, just the signature of the function
653- seed = [i .seed for i in replicas_settings ]
641+ all_seed = [i .seed for i in replicas_settings ]
654642 num_replicas = len (replicas_settings )
655643
656644 if impose_sumrule is None :
657645 impose_sumrule = "All"
658646
659- # Process input options. There are 2 options:
660- # 1. Scale the input
661- # 2. Concatenate log(x) to the input
662- use_feature_scaling = scaler is not None
663-
664- # When scaler is active we also want to do the subtraction of large x
665- # TODO: make it its own option (i.e., one could want to use this without using scaler)
666- subtract_one = use_feature_scaling
647+ ## Process the input data (x grid)
648+ # There a currently two options:
649+ # 1. Append log(x) to the input
650+ # 2. Scale the input
651+ do_nothing = lambda x : x
652+ model_input = {}
667653
668- # Feature scaling happens before the pdf model and changes x->(scaler(x), x),
669- # so it adds an input dimension
670- pdf_input_dimensions = 2 if use_feature_scaling else 1
671- # Adding of logs happens inside, but before the NN and adds a dimension there
672- nn_input_dimensions = 1 if use_feature_scaling else 2
654+ if scaler is None : # add log(x)
655+ use_feature_scaling = subtract_one = False
656+ # The PDF itself receives only x
657+ pdf_input_dimensions = 1
658+ # But the NN will see (x, log(x))
659+ nn_input_dimensions = 2
673660
674- # Define the main input
675- do_nothing = lambda x : x
676- if use_feature_scaling :
677- pdf_input = Input (shape = (None , pdf_input_dimensions ), batch_size = 1 , name = "scaledx_x" )
678- process_input = do_nothing
679- extract_nn_input = Lambda (lambda x : op .op_gather_keep_dims (x , 0 , axis = - 1 ), name = "x_scaled" )
680- extract_original = Lambda (lambda x : op .op_gather_keep_dims (x , 1 , axis = - 1 ), name = "pdf_input" )
681- else : # add log(x)
682661 pdf_input = Input (shape = (None , pdf_input_dimensions ), batch_size = 1 , name = "pdf_input" )
683662 process_input = Lambda (lambda x : op .concatenate ([x , op .op_log (x )], axis = - 1 ), name = "x_logx" )
684663 extract_original = do_nothing
685664 extract_nn_input = do_nothing
665+ else :
666+ use_feature_scaling = subtract_one = True
667+ # The NN will only receive x
668+ nn_input_dimensions = 1
669+ # But the PDF itself will receive both (x, scaler(x))
670+ pdf_input_dimensions = 2
686671
687- model_input = {"pdf_input" : pdf_input }
672+ pdf_input = Input (shape = (None , pdf_input_dimensions ), batch_size = 1 , name = "scaledx_x" )
673+ process_input = do_nothing
674+ extract_nn_input = Lambda (lambda x : op .op_gather_keep_dims (x , 0 , axis = - 1 ), name = "x_scaled" )
675+ extract_original = Lambda (lambda x : op .op_gather_keep_dims (x , 1 , axis = - 1 ), name = "pdf_input" )
688676
689677 if subtract_one :
678+ # TODO: make it its own option, even though now it only activates in the scaler if above
690679 input_x_eq_1 = [1.0 ]
691680 if use_feature_scaling :
692681 input_x_eq_1 = scaler (input_x_eq_1 )[0 ]
@@ -695,37 +684,9 @@ def _pdfNN_layer_generator(
695684 layer_x_eq_1 = op .numpy_to_input (np .array (input_x_eq_1 ).reshape (1 , 1 ), name = "x_eq_1" )
696685 model_input ["layer_x_eq_1" ] = layer_x_eq_1
697686
698- # the layer that multiplies the NN output by the preprocessing factor
699- apply_preprocessing_factor = Lambda (op .op_multiply , name = "prefactor_times_NN" )
700-
701- # Photon layer
702- layer_photon = AddPhoton (photons = photons , name = "add_photon" )
703-
704- # Basis rotation
705- basis_rotation = FlavourToEvolution (
706- flav_info = flav_info , fitbasis = fitbasis , name = "pdf_evolution_basis"
707- )
708-
709- # Evolution layer
710- layer_evln = FkRotation (output_dim = out , name = "pdf_FK_basis" )
711-
712- # Normalization and sum rules
713- if impose_sumrule :
714- sumrule_layer , integrator_input = generate_msr_model_and_grid (
715- fitbasis = fitbasis , mode = impose_sumrule , scaler = scaler , replica_seeds = seed
716- )
717- model_input ["xgrid_integration" ] = integrator_input
718- else :
719- sumrule_layer = lambda x : x
720-
721- compute_preprocessing_factor = Preprocessing (
722- flav_info = flav_info ,
723- name = PREPROCESSING_LAYER_ALL_REPLICAS ,
724- replica_seeds = seed ,
725- large_x = not subtract_one ,
726- )
687+ model_input ["pdf_input" ] = pdf_input
727688
728- # Create the actual NeuralNetwork PDF
689+ ## Create the actual NeuralNetwork PDF
729690 # loop over the settings for all replicas and generate a list of NN per replica
730691 # which will be then stack together and built into a single (input -> output) MetaModel
731692 # all PDFs _must_ share the same input layer
@@ -741,7 +702,21 @@ def _pdfNN_layer_generator(
741702 nn_pdfs = Lambda (lambda nns : op .stack (nns , axis = 1 ), name = f"stack_replicas" )(list_of_nn_pdfs )
742703 nn_replicas = MetaModel ({'NN_input' : x_input }, nn_pdfs , name = NN_LAYER_ALL_REPLICAS )
743704
744- # The NN subtracted by NN(1), if applicable
705+ ## Preprocessing factors:
706+ # the layer that multiplies the NN output by the preprocessing factor
707+ # This includes
708+ # - x^{a}(1-x)^{b}
709+ # - NN(x) - N(1.0)
710+ apply_preprocessing_factor = Lambda (op .op_multiply , name = "prefactor_times_NN" )
711+
712+ compute_preprocessing_factor = Preprocessing (
713+ flav_info = flav_info ,
714+ name = PREPROCESSING_LAYER_ALL_REPLICAS ,
715+ replica_seeds = all_seed ,
716+ large_x = not subtract_one ,
717+ )
718+
719+ # The NN subtracted by NN(1), if applicable, otherwise do nothing
745720 def nn_subtracted (x ):
746721 NNs_x = nn_replicas (x )
747722
@@ -752,6 +727,21 @@ def nn_subtracted(x):
752727
753728 return NNs_x
754729
730+ ## Unnormalized PDF
731+ # updf_r(x) = FkRotation( NN_r(input(x)) * preprocessing_layer_r(x) )
732+ # with _r: replica index
733+ # input: whatever processing is applied to the input
734+ # The preprocessing_layer and weights is specific to each replica
735+ # The final PDF will be in the 14 flavours evolution basis used in the FkTables
736+
737+ # Basis rotation
738+ basis_rotation = FlavourToEvolution (
739+ flav_info = flav_info , fitbasis = fitbasis , name = "pdf_evolution_basis"
740+ )
741+
742+ # Evolution layer
743+ layer_evln = FkRotation (output_dim = out , name = "pdf_FK_basis" )
744+
755745 def compute_unnormalized_pdf (x ):
756746 # Preprocess the input grid
757747 x_nn_input = extract_nn_input (x )
@@ -767,28 +757,39 @@ def compute_unnormalized_pdf(x):
767757 # Apply the preprocessing factor
768758 pref_NNs_x = apply_preprocessing_factor ([preprocessing_factors_x , NNs_x ])
769759
770- # Apply basis rotation if needed
760+ # Transform to FK basis, this is the full evolution basis
761+ # Rotate to the 9f evolution basis first before expanding up to 14f
762+ # TODO: make these two steps into one
771763 if not basis_rotation .is_identity ():
772764 pref_NNs_x = basis_rotation (pref_NNs_x )
773-
774- # Transform to FK basis
775765 PDFs_unnormalized = layer_evln (pref_NNs_x )
776766
777767 return PDFs_unnormalized
778768
779769 PDFs_unnormalized = compute_unnormalized_pdf (pdf_input )
780770
771+ ## Normalization and sum rules, produces normalized PDF
772+ # pdf_r(x) = updf_r(x) * Normalization(updf_r(integration_xgrid))
773+ # The normalization layer is shared across replicas (but evaluated at each replica separately)
774+ #
781775 if impose_sumrule :
776+ sumrule_layer , integrator_input = generate_msr_model_and_grid (
777+ fitbasis = fitbasis , mode = impose_sumrule , scaler = scaler , replica_seeds = all_seed
778+ )
779+ model_input ["xgrid_integration" ] = integrator_input
780+
781+ # We need a second unnormalized PDF evaluated on the integrated grid
782782 PDFs_integration_grid = compute_unnormalized_pdf (integrator_input )
783783
784+ # Photon contribution to the sum rule
784785 if photons :
785786 # add batch and flavor dimensions
786787 ph_tensor = op .numpy_to_tensor (photons .integral )
787788 photon_integrals = op .batchit (op .batchit (ph_tensor ))
788789 else :
789790 photon_integrals = op .numpy_to_tensor (np .zeros ((1 , num_replicas , 1 )))
790791
791- PDFs_normalized = sumrule_layer (
792+ PDFs = sumrule_layer (
792793 {
793794 "pdf_x" : PDFs_unnormalized ,
794795 "pdf_xgrid_integration" : PDFs_integration_grid ,
@@ -797,18 +798,21 @@ def compute_unnormalized_pdf(x):
797798 "photon_integral" : photon_integrals ,
798799 }
799800 )
800- PDFs = PDFs_normalized
801801 else :
802802 PDFs = PDFs_unnormalized
803+ sumrule_layer = lambda x : x
803804
805+ ## Include the photon in the PDF for QED-enabled fits
806+ # (by default the entry corresponding to the photon is set to 0)
804807 if photons :
808+ layer_photon = AddPhoton (photons = photons , name = "add_photon" )
805809 PDFs = layer_photon (PDFs )
806810
811+ # Return a PDF without a replica axis, to extract single replicas from an ensemble
807812 if not replica_axis :
808813 PDFs = Lambda (lambda pdfs : pdfs [:, 0 ], name = "remove_replica_axis" )(PDFs )
809814
810- pdf_model = MetaModel (model_input , PDFs , name = f"PDFs" , scaler = scaler )
811- return pdf_model
815+ return MetaModel (model_input , PDFs , name = f"PDFs" , scaler = scaler )
812816
813817
814818# TODO: is there a way of keeping sincronized the input of this function and _ReplicaSettings
@@ -899,7 +903,7 @@ def layer_generator(i_layer, nodes_out, activation):
899903 previous_layer = layer (previous_layer )
900904
901905 # Add dropout if any to the second to last layer
902- if dropout_rate > 0 and layer_idx == (len (hidden_layers ) - 1 ):
906+ if dropout_rate > 0 and layer_idx == (len (hidden_layers ) - 2 ):
903907 dropout_l = base_layer_selector ("dropout" , rate = dropout_rate )
904908 previous_layer = dropout_l (previous_layer )
905909
0 commit comments