33import java .util .ArrayList ;
44import java .util .Arrays ;
55import java .util .List ;
6+ import java .util .stream .Collectors ;
7+ import java .util .stream .IntStream ;
8+ import java .util .stream .Stream ;
69
710import org .apache .commons .math3 .linear .Array2DRowRealMatrix ;
811import org .apache .commons .math3 .linear .RealMatrix ;
@@ -67,7 +70,7 @@ public class FactorialDOE {
6770 * @param designFactorLevelArray
6871 * an array where each element specifies the number of levels for a design factor
6972 */
70- public static RealMatrix fullFactorial ( int [] designFactorLevelArray ) {
73+ public static RealMatrix fullFactorial ( Integer [] designFactorLevelArray ) {
7174 // calculate matrix size by number of combinations
7275 int length = designFactorLevelArray .length ;
7376 int combinationCount = 1 ;
@@ -150,10 +153,10 @@ public static RealMatrix fullFactorial2Level( int designFactorCount ) {
150153 * Generates a fractional factorial design matrix from a generator string.
151154 *
152155 * <p>
153- * This method creates a design matrix for a fractional factorial experiment based on a user-provided generator string.
154- * The generator string defines main factors and combination factors, optionally with a leading '+' or '-' sign for
155- * combination factors. Each main factor is treated as a 2-level factor (-1 and +1). Combination factors are computed
156- * as the row-wise product of the relevant main factor columns, with signs applied if specified.
156+ * This method creates a design matrix for a fractional factorial experiment based on a user-provided generator string. The generator
157+ * string defines main factors and combination factors, optionally with a leading '+' or '-' sign for combination factors. Each main
158+ * factor is treated as a 2-level factor (-1 and +1). Combination factors are computed as the row-wise product of the relevant main
159+ * factor columns, with signs applied if specified.
157160 * </p>
158161 *
159162 * <p>
@@ -197,12 +200,11 @@ public static RealMatrix fullFactorial2Level( int designFactorCount ) {
197200 * </p>
198201 *
199202 * @param generetor
200- * A generator string defining main factors and combination factors. Main factors are single letters,
201- * combination factors are multiple letters possibly prefixed with '+' or '-' to indicate sign.
203+ * A generator string defining main factors and combination factors. Main factors are single letters, combination factors are
204+ * multiple letters possibly prefixed with '+' or '-' to indicate sign.
202205 *
203206 * @throws IllegalArgumentException
204207 * If the generator string is invalid according to {@link FactorialUtility#validateGeneretor(String)}.
205- *
206208 * @see FactorialUtility#validateGeneretor(String)
207209 */
208210 public static RealMatrix fractionalFactorial ( String generetor ) {
@@ -244,7 +246,7 @@ public static RealMatrix fractionalFactorial( String generetor ) {
244246 List < String > factorCombinationSplit = Arrays .asList ( factorList .get ( combinationDesignFactorIndexValue ).split ( "" ) );
245247 if ( factorList .get ( combinationDesignFactorIndexValue ).startsWith ( "-" ) || factorList .get (
246248 combinationDesignFactorIndexValue ).startsWith ( "+" ) ) {
247- signOfCurrentCombinationDesignFactor = factorList .get ( combinationDesignFactorIndexValue ).split ( "" )[0 ];
249+ signOfCurrentCombinationDesignFactor = factorList .get ( combinationDesignFactorIndexValue ).split ( "" )[ 0 ];
248250 }
249251 List < Integer > matchingMainFactorFinalMatrixIndexList = new ArrayList <>();
250252 for ( int c = 0 ; factorCombinationSplit .size () > c ; c ++ ) {
@@ -271,9 +273,199 @@ public static RealMatrix fractionalFactorial( String generetor ) {
271273 }
272274 }
273275
274- log .info ("Fractional Factorial Result Matrix: " + finalDesignMatrix );
276+ log .info ( "Fractional Factorial Result Matrix: " + finalDesignMatrix );
275277
276278 return finalDesignMatrix ;
277279 }
278280
281+ // NOTE:
282+ // Combinations factors = which factors/levels are tested together
283+ // Resolution = how clearly we can separate their effects after testing
284+ // This method will generete teh optimal generetors for you based on the resolution value provided
285+ public static RealMatrix fractionalFactorialByResolution ( int n , int res ) {
286+ /*
287+ STEPS:
288+ determine minimum base factors from the total number of factors provided
289+ determine total number combination factors using resolution -1
290+ generete generator string to produce 2 level fractional factorial
291+ */
292+ // Validating resolution
293+ if ( res < 3 || res > 5 ) {
294+ throw new IllegalArgumentException ( "resolution number should be in range of 3 to 5" );
295+ }
296+
297+ // 1. Find minimum base factors needed
298+ Integer minFac = null ;
299+
300+ for (int k = res - 1 ; k < n ; k ++) {
301+ if (nFacAtRes (k , res ) >= n ) {
302+ minFac = k ;
303+ break ;
304+ }
305+ }
306+
307+ if (minFac == null ) {
308+ throw new IllegalArgumentException ("design not possible" );
309+ }
310+
311+ // 2. Check if we have enough letters (theoretical, but included for completeness)
312+ if (minFac > 26 ) { // Only 26 letters in alphabet
313+ throw new IllegalArgumentException ("design requires too many base-factors." );
314+ }
315+
316+ // 3. Get base factors as letters
317+ List <String > factors = new ArrayList <>();
318+ for (int i = 0 ; i < minFac ; i ++) {
319+ factors .add (String .valueOf ((char ) ('a' + i )));
320+ }
321+
322+ // 4. Generate combinations for extra factors
323+ List <String > extraFactors = new ArrayList <>();
324+ int needed = n - factors .size ();
325+
326+ // Generate combinations from length (res-1) up to length of factors
327+ for (int r = res - 1 ; r <= factors .size () && needed > 0 ; r ++) {
328+ // Generate all combinations of size r from factors
329+ List <List <String >> combos = generateCombinations (factors , r );
330+
331+ for (List <String > combo : combos ) {
332+ if (needed <= 0 ) break ;
333+
334+ // Join combination (e.g., ["a", "b"] -> "ab")
335+ String comboStr = String .join ("" , combo );
336+ extraFactors .add (comboStr );
337+ needed --;
338+ }
339+ }
340+
341+ // 5. Combine all factors
342+ List <String > allFactors = new ArrayList <>(factors );
343+ allFactors .addAll (extraFactors );
344+
345+ // 6. Create generator string
346+ String generator = String .join (" " , allFactors );
347+ return fractionalFactorial ( generator );
348+
349+ }
350+ // Helper method to calculate number of factors at given resolution
351+ private static int nFacAtRes (int k , int res ) {
352+ // This calculates total factors possible with k base factors at resolution res
353+ // For k base factors, total possible factors = k + C(k, res-1) + C(k, res) + ...
354+ int total = k ; // Base factors
355+
356+ // Add combinations starting from length (res-1) up to k
357+ for (int r = res - 1 ; r <= k ; r ++) {
358+ total += combinations (k , r );
359+ }
360+
361+ return total ;
362+ }
363+
364+ private static int combinations (int n , int k ) {
365+ if (k < 0 || k > n ) return 0 ;
366+ if (k == 0 || k == n ) return 1 ;
367+
368+ int result = 1 ;
369+ for (int i = 1 ; i <= k ; i ++) {
370+ result = result * (n - k + i ) / i ;
371+ }
372+ return result ;
373+ }
374+
375+
376+ // Helper to generate all combinations of size k from a list
377+ private static List <List <String >> generateCombinations (List <String > items , int k ) {
378+ List <List <String >> result = new ArrayList <>();
379+ if (k == 0 ) {
380+ result .add (new ArrayList <>());
381+ return result ;
382+ }
383+ if (k > items .size ()) {
384+ return result ;
385+ }
386+
387+ // Using backtracking to generate combinations
388+ generateCombinationsHelper (items , k , 0 , new ArrayList <>(), result );
389+ return result ;
390+ }
391+
392+ private static void generateCombinationsHelper (List <String > items , int k , int start ,
393+ List <String > current , List <List <String >> result ) {
394+ if (current .size () == k ) {
395+ result .add (new ArrayList <>(current ));
396+ return ;
397+ }
398+
399+ for (int i = start ; i < items .size (); i ++) {
400+ current .add (items .get (i ));
401+ generateCombinationsHelper (items , k , i + 1 , current , result );
402+ current .remove (current .size () - 1 );
403+ }
404+ }
405+
406+ // Alternative: Using Java Streams for a more functional approach (Java 8+)
407+ public static String createGeneratorStream (int n , int res ) {
408+ // Find minimum base factors
409+ int minFac = IntStream .range (res - 1 , n )
410+ .filter (k -> nFacAtRes (k , res ) >= n )
411+ .findFirst ()
412+ .orElseThrow (() -> new IllegalArgumentException ("design not possible" ));
413+
414+ if (minFac > 26 ) {
415+ throw new IllegalArgumentException ("design requires too many base-factors." );
416+ }
417+
418+ // Get base factors
419+ List <String > factors = IntStream .range (0 , minFac )
420+ .mapToObj (i -> String .valueOf ((char ) ('a' + i )))
421+ .collect ( Collectors .toList ());
422+
423+ // Generate extra factors using streams
424+ List <String > extraFactors = new ArrayList <>();
425+ int needed = n - factors .size ();
426+
427+ // Create stream of combination lengths
428+ Stream <Integer > lengths = IntStream .range (res - 1 , factors .size () + 1 )
429+ .boxed ();
430+
431+ // Flat map to get all combinations
432+ List <String > allCombinations = lengths
433+ .flatMap (r -> generateCombinationsStream (factors , r ).stream ())
434+ .collect (Collectors .toList ());
435+
436+ // Take only as many as needed
437+ extraFactors = allCombinations .stream ()
438+ .limit (needed )
439+ .collect (Collectors .toList ());
440+
441+ // Combine all factors
442+ List <String > allFactors = new ArrayList <>(factors );
443+ allFactors .addAll (extraFactors );
444+
445+ return String .join (" " , allFactors );
446+ }
447+
448+ // Generate combinations using streams
449+ private static List <String > generateCombinationsStream (List <String > items , int r ) {
450+ if (r == 0 ) {
451+ return List .of ("" );
452+ }
453+ if (r == 1 ) {
454+ return new ArrayList <>(items );
455+ }
456+
457+ List <String > result = new ArrayList <>();
458+ for (int i = 0 ; i <= items .size () - r ; i ++) {
459+ String first = items .get (i );
460+ List <String > remaining = items .subList (i + 1 , items .size ());
461+ List <String > smallerCombos = generateCombinationsStream (remaining , r - 1 );
462+
463+ for (String combo : smallerCombos ) {
464+ result .add (first + combo );
465+ }
466+ }
467+
468+ return result ;
469+ }
470+
279471}
0 commit comments