Skip to content

Commit 0127b81

Browse files
full factorial complete implementation
1 parent f7aea30 commit 0127b81

3 files changed

Lines changed: 483 additions & 55 deletions

File tree

src/main/java/com/jdoe/algorithms/FactorialDOE.java

Lines changed: 279 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.jdoe.algorithms;
22

3-
import java.util.ArrayList;
4-
import java.util.Arrays;
5-
import java.util.List;
3+
import java.util.*;
64
import java.util.stream.Collectors;
75
import java.util.stream.IntStream;
86
import 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

Comments
 (0)