@@ -2685,12 +2685,14 @@ def __init__(
26852685 path : pathlib .Path | None = None ,
26862686 nesting : list [list ] | None = None ,
26872687 target_chi : float = 1.0 ,
2688+ headers : list [str ] | None = None ,
26882689 ** kwargs ,
26892690 ):
26902691 self .last_beta = None
26912692 self .chi_factors = None
26922693 self .target_chi = target_chi
26932694 self .nesting = nesting
2695+ self .headers = headers
26942696
26952697 if path is None :
26962698 path = pathlib .Path ("./" )
@@ -2699,24 +2701,44 @@ def __init__(
26992701
27002702 super ().__init__ (** kwargs )
27012703
2704+ self ._log_array : np .ndarray | None = None
2705+
2706+ @property
2707+ def log_array (self , headers : list [str ] | None = None ):
2708+ if self ._log_array is None :
2709+ if self .headers is None :
2710+
2711+ def append_sub_indices (elements , header ):
2712+ values = []
2713+ for ii , elem in enumerate (elements ):
2714+ heads = header + f"[{ ii } ]"
2715+ if isinstance (elem , Iterable ):
2716+ values += append_sub_indices (elem , heads )
2717+ else :
2718+ values += [heads ]
2719+ return values
2720+
2721+ headers = []
2722+ for ii , elem in enumerate (self .misfit_tree_indices ):
2723+ headers += append_sub_indices (elem , f"[{ ii } ]" )
2724+ self .headers = headers
2725+
2726+ dtype = np .dtype (
2727+ [("Iterations" , np .int32 )] + [(h , np .float32 ) for h in self .headers ]
2728+ )
2729+ self ._log_array = np .rec .fromrecords ((), dtype = dtype )
2730+
2731+ return self ._log_array
2732+
27022733 def initialize (self ):
27032734 self .last_beta = self .invProb .beta
27042735 self .multipliers = self .invProb .dmisfit .multipliers
27052736 self .scalings = np .ones_like (self .multipliers ) # Everyone gets a fair chance
27062737 self .misfit_tree_indices = self .parse_by_nested_levels (self .nesting )
2707- # def append_labels(_, depth):
2708- # return f"[{depth}]"
2709- #
2710- # with open(self.filepath, "w", encoding="utf-8") as f:
2711- # f.write("Logging of [scaling * chi factor] per misfit function.\n\n")
2712- #
2713- # header = "Iterations\t"
2714- # for elem in recursions(self.nested_misfits, append_labels):
2715- # header += "\t".join(f"Misfit [{elem}]")
2716- #
2717- # f.write("\n")
2718-
2719- def scalings_by_level (
2738+
2739+ self .write_log ()
2740+
2741+ def scale_by_level (
27202742 self , nested_values , nested_indices , ratio , scaling_vector : np .ndarray | None
27212743 ):
27222744 """
@@ -2725,6 +2747,20 @@ def scalings_by_level(
27252747 The maximum chi-factor at each level is used to determine scaling factors
27262748 for the misfit functions at that level. The scaling factors are then propagated
27272749 down to the next level of the nested structure.
2750+
2751+ Parameters
2752+ ----------
2753+ nested_values : list
2754+ Nested list of misfit residuals.
2755+
2756+ nested_indices : list
2757+ Nested list of indices corresponding to the misfit residuals.
2758+
2759+ ratio : float
2760+ Ratio of current beta to last beta.
2761+
2762+ scaling_vector : np.ndarray, optional
2763+ Current scaling vector to be updated.
27282764 """
27292765 if scaling_vector is None :
27302766 scaling_vector = np .ones (len (self .invProb .dmisfit .multipliers ))
@@ -2733,7 +2769,7 @@ def scalings_by_level(
27332769 flat_indices = []
27342770 for elem , indices in zip (nested_values , nested_indices ):
27352771
2736- # Reach the outer most level
2772+ # Reach the outermost level
27372773 if not isinstance (indices , list ) or (
27382774 len (indices ) == 1 and not isinstance (indices [0 ], list )
27392775 ):
@@ -2759,9 +2795,7 @@ def scalings_by_level(
27592795 scaling_vector [group_ind ] = np .maximum (
27602796 ratio , scale * scaling_vector [group_ind ]
27612797 )
2762- scaling_vector = self .scalings_by_level (
2763- elem , indices , ratio , scaling_vector
2764- )
2798+ scaling_vector = self .scale_by_level (elem , indices , ratio , scaling_vector )
27652799
27662800 return scaling_vector
27672801
@@ -2771,7 +2805,7 @@ def endIter(self):
27712805 self .nesting , self .invProb .residuals
27722806 )
27732807
2774- scalings = self .scalings_by_level (
2808+ scalings = self .scale_by_level (
27752809 nested_residuals , self .misfit_tree_indices , ratio , None
27762810 )
27772811
@@ -2781,24 +2815,26 @@ def endIter(self):
27812815 # Normalize total phi_d with scalings
27822816 self .invProb .dmisfit .multipliers = self .multipliers * self .scalings
27832817 self .last_beta = self .invProb .beta
2784- # self.write_log()
2818+
2819+ # Log the scaling factors
2820+ self .write_log ()
27852821
27862822 def parse_by_nested_levels (
27872823 self , nesting : list [Iterable ], values : Iterable | None = None
27882824 ) -> Iterable :
27892825 """
2790- Replace leaf elements of `nesting` with values from `flat ` (in order).
2791- Assumes the number of leaf positions equals len(flat ).
2826+ Replace leaf elements of `nesting` with values from `values ` (in order).
2827+ Assumes the number of leaf positions equals len(values ).
27922828
27932829 Parameters:
27942830 - nesting: arbitrarily nested list structure; leaves are non-list values
2795- - flat : flat iterable whose values will fill the leaves in order
2831+ - values : flat iterable whose values will fill the leaves in order
27962832
27972833 Returns:
2798- - A new nested structure with leaves replaced by values from `flat `.
2834+ - A new nested structure with leaves replaced by values from `values `.
27992835
28002836 Raises:
2801- - ValueError if `flat ` has fewer or more elements than required by `nesting`.
2837+ - ValueError if `values ` has fewer or more elements than required by `nesting`.
28022838 """
28032839 indices = np .arange (len (self .invProb .dmisfit .objfcts ))
28042840 if nesting is None :
@@ -2836,16 +2872,19 @@ def write_log(self):
28362872 """
28372873 Write the scaling factors to the log file.
28382874 """
2839- with open (self .filepath , "a" , encoding = "utf-8" ) as f :
2840- f .write (
2841- f"{ self .opt .iter } \t "
2842- + "\t " .join (
2843- f"{ multi :.2e} * { chi :.2e} "
2844- for multi , chi in zip (
2845- self .invProb .dmisfit .multipliers , self .chi_factors
2846- )
2847- )
2848- + "\n "
2875+ self ._log_array = np .append (
2876+ self .log_array ,
2877+ np .rec .fromrecords (
2878+ tuple ([getattr (self .opt , "iter" , 0 )] + self .scalings .tolist ()),
2879+ dtype = self .log_array .dtype ,
2880+ ),
2881+ )
2882+ with open (self .filepath , "w" , encoding = "utf-8" ) as f :
2883+ np .savetxt (
2884+ f ,
2885+ self .log_array ,
2886+ header = "Iterations - Scaling per misfit" ,
2887+ fmt = ["%d" ] + ["%0.2e" ] * (len (self ._log_array .dtype ) - 1 ),
28492888 )
28502889
28512890
0 commit comments