11package com .jdoe .algorithms ;
22
3- import java .util .ArrayList ;
4- import java .util .Arrays ;
5- import java .util .List ;
3+ import java .util .*;
64import java .util .stream .Collectors ;
75import java .util .stream .IntStream ;
86import java .util .stream .Stream ;
@@ -76,7 +74,6 @@ public static RealMatrix fullFactorial(Integer[] designFactorLevelArray) {
7674 for (int i = 0 ; i < length ; i ++) {
7775 combinationCount *= designFactorLevelArray [i ];
7876 }
79- log .debug ("Total Combinations: " + combinationCount );
8077 // create matrix for storing combinations
8178 Array2DRowRealMatrix matrixFactory = new Array2DRowRealMatrix ();
8279 RealMatrix matrix = matrixFactory .createMatrix (combinationCount , length );
@@ -89,7 +86,6 @@ public static RealMatrix fullFactorial(Integer[] designFactorLevelArray) {
8986 temp /= designFactorLevelArray [colNum ];
9087 }
9188 }
92- log .debug (matrix .toString ());
9389 return matrix ;
9490 }
9591
@@ -127,7 +123,6 @@ public static RealMatrix fullFactorial2Level(int designFactorCount) {
127123 Integer combinationCount = (int ) Math .pow (2 , designFactorCount );
128124 Array2DRowRealMatrix matrixFactory = new Array2DRowRealMatrix ();
129125 RealMatrix matrix = matrixFactory .createMatrix (combinationCount , designFactorCount );
130- log .debug ("Total Combinations: " + combinationCount );
131126 // populate matrix
132127 // Generate all binary numbers from 0 to (2^k - 1)
133128 for (int i = 0 ; i < combinationCount ; i ++) {
@@ -140,7 +135,6 @@ public static RealMatrix fullFactorial2Level(int designFactorCount) {
140135 matrix .setEntry (i , j , bitValue == 0 ? -1 : 1 );
141136 }
142137 }
143- log .debug (matrix .toString ());
144138 return matrix ;
145139 }
146140
@@ -264,7 +258,6 @@ public static RealMatrix fractionalFactorial(String generetor) {
264258 }
265259 }
266260
267- log .info ("Fractional Factorial Result Matrix: " + finalDesignMatrix );
268261
269262 return finalDesignMatrix ;
270263 }
@@ -375,4 +368,282 @@ public static RealMatrix fractionalFactorialByResolution(int n, int res) {
375368
376369 }
377370
371+ /**
372+ * Finds the optimal generator string for a 2-level fractional-factorial design
373+ * with the specified number of factors and erased factors.
374+ *
375+ * <p>
376+ * This method performs an exhaustive search (with optional limit) to find the
377+ * generator string that minimizes aliasing between low-order interactions.
378+ * </p>
379+ *
380+ * @param nFactors The number of factors in the full factorial design
381+ * @param nErased The number of factors to "remove" to create the fractional design
382+ * @param maxAttempts The maximum number of models to attempt. Positive values limit attempts,
383+ * zero or negative values indicate all combinations should be attempted.
384+ * @return An array containing: [0] generator string, [1] alias map, [2] alias vector
385+ * @throws IllegalArgumentException if parameters are invalid or design is too complex
386+ * @see #fracFactAliasing(RealMatrix)
387+ */
388+ public static Object [] fracFactOpt (int nFactors , int nErased , int maxAttempts ) {
389+ if (nFactors > 20 ) {
390+ throw new IllegalArgumentException ("Design too big, use 20 factors or less" );
391+ }
392+
393+ if (nFactors < 2 ) {
394+ throw new IllegalArgumentException ("Design too small" );
395+ }
396+
397+ if (nErased < 0 ) {
398+ throw new IllegalArgumentException ("Number of erased factors must be non-negative" );
399+ }
400+
401+ int nMainFactors = nFactors - nErased ;
402+
403+ // Calculate number of possible aliases
404+ int nAliases = 0 ;
405+ for (int i = 2 ; i <= nMainFactors ; i ++) {
406+ nAliases += FactorialUtility .combinations (nMainFactors , i );
407+ }
408+
409+ if (nErased > FactorialUtility .combinations (nAliases , nErased )) {
410+ throw new IllegalArgumentException ("Too many erased factors to create aliasing" );
411+ }
412+
413+ // Generate main factor names (a, b, c, ...)
414+ List <String > mainFactorNames = new ArrayList <>();
415+ for (int i = 0 ; i < nMainFactors ; i ++) {
416+ mainFactorNames .add (String .valueOf ((char ) ('a' + i )));
417+ }
418+
419+ String mainDesign = String .join (" " , mainFactorNames );
420+
421+ // Generate all possible aliases
422+ List <List <Integer >> aliases = new ArrayList <>();
423+ for (int r = 2 ; r <= nMainFactors ; r ++) {
424+ aliases .addAll (FactorialUtility .generateCombinationsIndices (nMainFactors , r ));
425+ }
426+
427+ // Sort by combination length and then lexicographically
428+ aliases .sort ((a , b ) -> {
429+ if (a .size () != b .size ()) {
430+ return Integer .compare (b .size (), a .size ()); // Reverse order
431+ }
432+ for (int i = 0 ; i < a .size (); i ++) {
433+ int cmp = Integer .compare (a .get (i ), b .get (i ));
434+ if (cmp != 0 ) return cmp ;
435+ }
436+ return 0 ;
437+ });
438+
439+ String bestDesign = null ;
440+ List <String > bestMap = new ArrayList <>();
441+ double [] bestVector = new double [nFactors ];
442+ Arrays .fill (bestVector , nFactors );
443+
444+ int designRows = (int ) Math .pow (2 , nMainFactors );
445+
446+ // Try combinations of aliases
447+ List <List <List <Integer >>> allCombinations = FactorialUtility .generateCombinationsFromList (aliases , nErased );
448+
449+ // Limit attempts if needed
450+ if (maxAttempts > 0 && allCombinations .size () > maxAttempts ) {
451+ allCombinations = allCombinations .subList (0 , maxAttempts );
452+ }
453+
454+ for (List <List <Integer >> aliasing : allCombinations ) {
455+ List <String > aliasingNames = new ArrayList <>();
456+ for (List <Integer > alias : aliasing ) {
457+ StringBuilder sb = new StringBuilder ();
458+ for (Integer idx : alias ) {
459+ sb .append (mainFactorNames .get (idx ));
460+ }
461+ aliasingNames .add (sb .toString ());
462+ }
463+
464+ String aliasingDesign = String .join (" " , aliasingNames );
465+ String completeDesign = mainDesign + " " + aliasingDesign ;
466+
467+ RealMatrix design = fractionalFactorial (completeDesign );
468+
469+ if (design .getRowDimension () == designRows && design .getColumnDimension () == nFactors ) {
470+ Object [] aliasResult = fracFactAliasing (design );
471+ @ SuppressWarnings ("unchecked" )
472+ List <String > aliasMap = (List <String >) aliasResult [0 ];
473+ double [] aliasVector = (double []) aliasResult [1 ];
474+
475+ if (FactorialUtility .compareVectors (aliasVector , bestVector ) < 0 ) {
476+ bestDesign = completeDesign ;
477+ bestMap = new ArrayList <>(aliasMap );
478+ bestVector = Arrays .copyOf (aliasVector , aliasVector .length );
479+ }
480+ }
481+ }
482+
483+ return new Object []{bestDesign , bestMap , bestVector };
484+ }
485+
486+ /**
487+ * Finds the aliasings in a design, given the contrasts.
488+ *
489+ * <p>
490+ * This method analyzes a fractional factorial design matrix to identify which factors and
491+ * interactions are aliased with each other. In fractional factorial designs, some effects
492+ * are confounded (aliased) with others due to the reduced number of experimental runs.
493+ * </p>
494+ *
495+ * <p>
496+ * The method works by:
497+ * <ol>
498+ * <li>Computing the contrast matrix for all possible factor combinations</li>
499+ * <li>Grouping factor combinations that have identical contrasts (these are aliased)</li>
500+ * <li>Generating a human-readable alias map showing which factors/interactions are aliased</li>
501+ * <li>Creating an alias vector that quantifies the degree of aliasing between different order interactions</li>
502+ * </ol>
503+ * </p>
504+ *
505+ * <p>
506+ * Example output for a 2^3-1 design with generator "a b -ab":
507+ * <pre>
508+ * Alias map: ["a = bc", "b = ac", "c = ab", "abc = 1"]
509+ * </pre>
510+ * This shows that factor 'a' is aliased with the bc interaction, and so on.
511+ * </p>
512+ *
513+ * <p>
514+ * The alias vector provides quantitative information about the aliasing structure.
515+ * It can be converted to a matrix using utility methods to analyze how many aliasings
516+ * exist between i-th and j-th order interactions.
517+ * </p>
518+ *
519+ * @param design A design matrix like those returned by {@link #fractionalFactorial(String)}
520+ * or {@link #fractionalFactorialByResolution(int, int)}. The matrix should contain
521+ * factor levels coded as -1 and +1.
522+ * @return An array containing:
523+ * <ul>
524+ * <li>[0] - A List<String> representing the alias map. Each string shows a group of
525+ * aliased factors/interactions in the format "a = bc = def"</li>
526+ * <li>[1] - A double[] representing the alias vector, which quantifies the aliasing
527+ * between different order interactions</li>
528+ * </ul>
529+ * @throws IllegalArgumentException if the number of factors exceeds 20 (to prevent excessive computation)
530+ * @see #fracFactOpt(int, int, int)
531+ * @see #fractionalFactorial(String)
532+ * @see #fractionalFactorialByResolution(int, int)
533+ */
534+ public static Object [] fracFactAliasing (RealMatrix design ) {
535+ int nRounds = design .getRowDimension ();
536+ int nFactors = design .getColumnDimension ();
537+
538+ if (nFactors > 20 ) {
539+ throw new IllegalArgumentException ("Design too big, use 20 factors or less" );
540+ }
541+
542+ // Generate factor names (a, b, c, ...)
543+ char [] factorNames = new char [nFactors ];
544+ for (int i = 0 ; i < nFactors ; i ++) {
545+ factorNames [i ] = (char ) ('a' + i );
546+ }
547+
548+ // Generate all combinations of factors
549+ List <List <Integer >> allCombinations = new ArrayList <>();
550+ for (int r = 1 ; r <= nFactors ; r ++) {
551+ allCombinations .addAll (FactorialUtility .generateCombinationsIndices (nFactors , r ));
552+ }
553+
554+ // Group combinations by their contrast values
555+ Map <String , List <List <Integer >>> aliases = new HashMap <>();
556+
557+ for (List <Integer > combination : allCombinations ) {
558+ // Calculate contrast for this combination
559+ double [] contrast = new double [nRounds ];
560+ for (int i = 0 ; i < nRounds ; i ++) {
561+ contrast [i ] = 1.0 ;
562+ for (Integer factorIdx : combination ) {
563+ contrast [i ] *= design .getEntry (i , factorIdx );
564+ }
565+ }
566+
567+ // Create a key from the contrast array
568+ String key = Arrays .toString (contrast );
569+
570+ if (!aliases .containsKey (key )) {
571+ aliases .put (key , new ArrayList <>());
572+ }
573+ aliases .get (key ).add (combination );
574+ }
575+
576+ // Process aliases into readable format
577+ List <List <List <Integer >>> aliasesList = new ArrayList <>(aliases .values ());
578+
579+ // Sort the aliases list
580+ aliasesList .sort ((list1 , list2 ) -> {
581+ // Compare by sizes of combinations
582+ List <Integer > sizes1 = new ArrayList <>();
583+ List <Integer > sizes2 = new ArrayList <>();
584+
585+ for (List <Integer > l : list1 ) sizes1 .add (l .size ());
586+ for (List <Integer > l : list2 ) sizes2 .add (l .size ());
587+
588+ Collections .sort (sizes1 );
589+ Collections .sort (sizes2 );
590+
591+ for (int i = 0 ; i < Math .min (sizes1 .size (), sizes2 .size ()); i ++) {
592+ int cmp = Integer .compare (sizes1 .get (i ), sizes2 .get (i ));
593+ if (cmp != 0 ) return cmp ;
594+ }
595+
596+ return Integer .compare (sizes1 .size (), sizes2 .size ());
597+ });
598+
599+ List <String > aliasesReadable = new ArrayList <>();
600+ double [][] aliasMatrix = new double [nFactors ][nFactors ];
601+
602+ for (List <List <Integer >> aliasGroup : aliasesList ) {
603+ List <String > groupNames = new ArrayList <>();
604+ for (List <Integer > combination : aliasGroup ) {
605+ StringBuilder sb = new StringBuilder ();
606+ for (Integer idx : combination ) {
607+ sb .append (factorNames [idx ]);
608+ }
609+ groupNames .add (sb .toString ());
610+ }
611+
612+ String aliasReadable = String .join (" = " , groupNames );
613+ aliasesReadable .add (aliasReadable );
614+
615+ // Update alias matrix
616+ List <Integer > sizes = new ArrayList <>();
617+ for (List <Integer > combination : aliasGroup ) {
618+ sizes .add (combination .size ());
619+ }
620+
621+ for (int i = 0 ; i < sizes .size (); i ++) {
622+ for (int j = i + 1 ; j < sizes .size (); j ++) {
623+ int size1 = sizes .get (i );
624+ int size2 = sizes .get (j );
625+ if (size1 <= size2 ) {
626+ aliasMatrix [size1 - 1 ][size2 - 1 ] += 1 ;
627+ } else {
628+ aliasMatrix [size2 - 1 ][size1 - 1 ] += 1 ;
629+ }
630+ }
631+ }
632+ }
633+
634+ // Convert matrix to vector using aliasVectorIndices logic
635+ int vectorSize = nFactors * (nFactors + 1 ) / 2 ;
636+ double [] aliasVector = new double [vectorSize ];
637+ int idx = 0 ;
638+
639+ for (int i = 0 ; i < nFactors ; i ++) {
640+ for (int j = i ; j < nFactors ; j ++) {
641+ aliasVector [idx ++] = aliasMatrix [i ][j ];
642+ }
643+ }
644+
645+ return new Object []{aliasesReadable , aliasVector };
646+ }
647+
648+
378649}
0 commit comments