Engineering Blog

Technical insights from Grid Dynamics engineers

A Tensor-Based Approach to Product Configuration

A Tensor-Based Approach to Product Configuration

Illia Krokhmalov · Oct 18, 2025

Building a Custom CPQ System for Configurable Furniture

If you’ve ever built or worked on an e-commerce system, you may have run into this situation: a “product” isn’t really a single item at all, but a family of variations sharing the same identity. At first, you might represent each variation as a separate SKU — but as soon as the product becomes customizable across multiple parameters, the number of possible combinations grows quickly, and the catalog starts to feel less like a list and more like a combinatorial explosion.

This is where the Configure, Price, Quote (CPQ) domain comes in. CPQ systems let you define a product as a set of parameters and rules, instead of trying to enumerate every possible variant. A user selects values step-by-step, and the system determines whether the configuration is valid and what it should cost.

However, once a product begins to vary across many parameters — and especially when those parameters influence each other — the configuration space grows quickly. At that point, the product can no longer be treated as a simple list of predefined SKUs; its structure becomes combinatorial.

There are established CPQ platforms designed for such problems, but in practice, applying a general-purpose system to a highly specific product model can be challenging. In this case, the client’s configuration logic, pricing rules, and manufacturing constraints were tightly interconnected in ways that were not easily expressed in standard CPQ rule frameworks.

One of our clients had reached exactly this point: they were working with a product family whose number of valid configurations had grown into the hundreds of thousands. Updates were slow, lead times were inconsistent, and modeling logic had become difficult to reason about.

Rather than treating the product as a giant lookup table, we re-modeled it as a tensor-structured parameter space — where each parameter is a dimension, and pricing and constraints are computed algorithmically rather than listed. This approach allowed us to evaluate availability, price, and compatibility in real time, even in high-dimensional configuration spaces.


Client / Business Problem

  1. High Product Variability
    Furniture products exhibit extraordinary variability: the same sofa can exist in numerous combinations of fillings, upholstery fabrics, cushion configurations, or modular compositions. Treating each variation as a separate SKU had led to exponential catalog growth and operational strain.

  2. Catalog Scalability Limits
    Once the catalog hit a million SKUs, updating and maintaining it became unsustainable. Performance, synchronization, and data freshness across multiple systems started breaking down.

  3. Virtual Configurations (MTO)
    Most combinations are virtual — Made to Order (MTO) — and only materialize when purchased. Generating full entries for all hypothetical configurations inflated data storage without adding value.

  4. Cross‑SKU Dependencies
    Shared upstream factors, such as fabric cost or supplier lead times, affected thousands of configurations simultaneously. Manual updates were complex, error‑prone, and costly.


Solution / Insights

Through joint discovery workshops and iterative design with the client’s engineering and merchandising teams, we re‑conceptualized the product model:

  1. Treat all variations as mathematical transformations of a single configurable product.
  2. Express relationships—such as price or delivery time—as functions of key parameters.
  3. Enable automatic propagation of upstream changes through functional dependencies.
  4. Incorporate manufacturing constraints directly into the model.
  5. Build an “Explain” mechanism to describe why a configuration is unavailable and how to adjust it.

This architecture drastically reduced computational and data overhead—by up to three orders of magnitude compared to flat catalogs.


Key Modeling Ideas

  1. A product item can be represented as a mapping of parameters to functions.
    fconfig:PFf_{config} : P \rightarrow F

  2. Parameterized product configurations can be seen as dense, high‑dimensional datasets rather than sparse catalogs.

  3. Instead of storing every SKU, we compute values on demand using tensor operations—similar to how multidimensional arrays are handled in numerical computing frameworks.

  4. This allows functional logic (e.g., pricing formulas, manufacture and delivery time calculations) to be expressed as mathematical relations, not hard‑coded tables.


Tensor‑Based Architecture

At the heart of the system is a multidimensional tensor where the first dimensions correspond to parameters, and the last dimension stores function values:

T[P1,P2,...,Pn,F]T[P_1, P_2, ..., P_n, F]

An optional Boolean tensor/mask represents availability. Selecting parameters becomes equivalent to slicing the tensor (T[:,:,1,...]), and retrieving function values corresponds to reading the resulting vector of F.

Advantages:

  • Compact representation and accelerated computation through BLAS/LAPACK (NumPy) or ND4J (Java).
  • Natural fit for GPU or distributed acceleration.
  • Straightforward extensibility for contextual computations (e.g., promotions or personalization).

We can say that instead of an index-based search approach, we are effectively performing a full scan. This is entirely reasonable when the data is stored compactly and the operations are optimized. There is a trade-off: when the domain is very large and the data is sparse, index-based retrieval is more efficient; however, when the domain is relatively moderate in size (e.g., <10^7) and densely packed, iterating directly over the tensor can be faster and simpler than performing multi-index lookups.

The following demonstration code illustrates the core principles of building and operating the tensor model, without domain-specific business logic. It is included to clarify the mechanics of slicing, constraint masking, and functional evaluation.

Demonstration
""" This demo models a configurable product as a multidimensional tensor where: - Each dimension corresponds to a parameter (fabric, frame, fill, etc.) - Each cell contains the evaluated function values (price, lead time, etc.) Filtering is performed by slicing and masking the tensor, which avoids searching or enumerating individual SKUs. This enables fast CPQ navigation and availability info. """ from collections import namedtuple, defaultdict import numpy as np import math # Minimal helper: namedtuple with __str__/__repr__ → name (fallback to tuple repr) def ntuple(typename: str, field_names) -> type: cls = namedtuple(typename, field_names) cls.__str__ = cls.__repr__ = lambda self: getattr(self, 'name', tuple.__repr__(self)) return cls # -- Attributes Ontology -- Frame = ntuple("Frame",["name", "width", "height", "fabric_surface", "fill_volume_cubic_ft", "legs", "assembly_bdays"]) FillMaterial = ntuple("FillMaterial", ["name", "price_per_cubic_ft", "availability"]) WoodSpecies = ntuple("WoodSpecies", ["name", "price_per_leg"]) WoodFinish = ntuple("WoodFinish", ["name", "species", "shade", "swatch_color"]) Cushion = ntuple("Cushion", ["name", "back", "seat"]) Fabric = ntuple("Fabric", ["name", "price_per_inch", "availability", "fabric_color", "shade", "discount_pct"]) Shade = ntuple("Shade", ["name"]) class Shades: LIGHT = Shade("Light") DARK = Shade("Dark") class Fabrics: COTTON = Fabric("Cotton", 1, 3, "Natural", Shades.LIGHT, 0) LINEN = Fabric("Linen", 2, 3, "Sand", Shades.LIGHT, 5) VELVET = Fabric("Velvet", 3, 10, "Charcoal", Shades.DARK, 10) WOOL = Fabric("Wool", 4, 6, "Ivory", Shades.LIGHT, 0) CHENILLE = Fabric("Chenille", 5, 8, "Mocha", Shades.DARK, 15) class WoodSpeciesTypes: OAK = WoodSpecies(name="Oak", price_per_leg=9) WALNUT = WoodSpecies(name="Walnut", price_per_leg=12) class WoodFinishes: OAK_LIGHT = WoodFinish(name="Oak_Light", species=WoodSpeciesTypes.OAK, shade=Shades.LIGHT, swatch_color="#d7c59a") OAK_DARK = WoodFinish(name="Oak_Dark", species=WoodSpeciesTypes.OAK, shade=Shades.DARK, swatch_color="#7a5f3b") WALNUT_LIGHT = WoodFinish(name="Walnut_Light", species=WoodSpeciesTypes.WALNUT, shade=Shades.LIGHT, swatch_color="#b49364") WALNUT_DARK = WoodFinish(name="Walnut_Dark", species=WoodSpeciesTypes.WALNUT, shade=Shades.DARK, swatch_color="#5a3e28") class Frames: SMALL = Frame(name="Small", width=4, height=3, fabric_surface=4 * 3, fill_volume_cubic_ft=4, legs=4, assembly_bdays=5) MEDIUM = Frame(name="Medium", width=5, height=3, fabric_surface=5 * 3, fill_volume_cubic_ft=5, legs=4, assembly_bdays=7) LARGE = Frame(name="Large", width=6, height=3, fabric_surface=6 * 3, fill_volume_cubic_ft=6, legs=4, assembly_bdays=7) class FillMaterials: FOAM = FillMaterial(name="Foam", price_per_cubic_ft=7, availability=2) DOWN = FillMaterial(name="Down", price_per_cubic_ft=13, availability=14) class Cushions: X1X1 = Cushion("1x1", 1, 1) X1X2 = Cushion("1x2", 1, 2) X2X2 = Cushion("2x2", 2, 2) X3X3 = Cushion("3x3", 3, 3) # -- Helper functions -- def vectorize_func(*funcs, dtype=np.float32): len_funcs = len(funcs) def vect_funct(params): funcs_tuple = np.zeros(len_funcs, dtype=dtype) for idx, func in enumerate(funcs): funcs_tuple[idx] = func(params, funcs_tuple) return funcs_tuple return vect_funct def objvec(lst): a = np.empty(len(lst), dtype=object) a[:] = lst return a # -------- Engine -------- # -- Tensor calculation -- def prepare_tensor(params, funcs, dtype, cfuncs=None): """ Build the dense product tensor F over the full parameter grid. If constraint_funcs is provided, also build a boolean constraint tensor C (same grid). Returns: (F_tensor, C_tensor_or_None) """ # Preserve param order; avoid dict(params) param_values = [objvec(values) for _, values in params] param_tensor = np.stack(np.meshgrid(*param_values, indexing='ij'), axis=-1).astype(object) # Functions tensor (F) vector_func = vectorize_func(*funcs, dtype=dtype) tensor_func = np.vectorize(vector_func, signature="(n)->(m)") f_tensor = tensor_func(param_tensor) # Optional constraints tensor (C) c_tensor = None if cfuncs: def c_vector(params_tuple, f_values): return np.array([c(params_tuple, f_values) for c in cfuncs], dtype=np.bool) apply_c = np.vectorize(c_vector, signature="(n),(m)->(k)") c_tensor = apply_c(param_tensor, f_tensor) c_tensor = c_tensor.prod(axis=-1, keepdims=True, dtype=np.bool) return f_tensor, c_tensor # --- Slice/advanced-index builder for parameter filters --- def build_params_filter_slice(params, params_filter_request): params_slice_list = [] param_filters = {p_name: (op, val) for p_name, op, val in params_filter_request} for param_name, param_values in params: if param_filter := param_filters.get(param_name): op, val = param_filter match op: case np.equal: params_slice_list.append(slice(param_values.index(val), param_values.index(val) + 1)) case _: raise RuntimeError("Unsupported param filter operator {}".format(op)) pass else: params_slice_list.append(slice(None)) params_slice_list.append(slice(None)) return tuple(params_slice_list) def build_props_filter_mask(f_tensor, funcs_list, request_props): """ Build a boolean mask selecting configurations that satisfy property constraints (e.g. price <= 500). Operates over the last axis (function dimension). """ mask = np.ones(f_tensor.shape[:-1], dtype=np.bool) funcs_index_dict = {f.__name__: i for i, f in enumerate(funcs_list)} for prop_name, op, val in request_props: data = f_tensor[..., funcs_index_dict[prop_name]] mask &= op(data, val) return mask def calc_unavailable_params(params, param_filters, f_mask): """ For each parameter value, check whether any valid configuration remains if that value is chosen (other filters remain fixed). Used for: "which options are disabled and why". """ filtered_params = {name for (name, _op, _val) in param_filters} result = defaultdict(list) for dim_idx, (pname, domain_values) in enumerate(params): if pname in filtered_params: continue # Skip parameters that already have an explicit filter # Probe each candidate value by fixing only this dimension; all other dims remain as-is for j, value in enumerate(domain_values): slc = [slice(None)] * f_mask.ndim slc[dim_idx] = slice(j, j + 1) if not np.any(f_mask[tuple(slc)]): result[pname].append(value) return result def apply_request(params, funcs_list, f_tensor, request_dict, c_tensor=None): # 1) Build per-dimension indexer from parameter filters and slice the tensor param_filters = request_dict.get('params', []) params_slice_filter = build_params_filter_slice(params, param_filters) # 2) Apply params selection f_tensor = f_tensor[params_slice_filter] # 3a) Build property mask on the sliced tensor f_mask = build_props_filter_mask(f_tensor, funcs_list, request_dict.get('props', [])) # 3b) Apply constraints (c_tensor) if provided if c_tensor is not None: f_mask &= c_tensor[params_slice_filter][..., 0] # 4) Apply property mask; compute indices/ranges count = int(f_mask.sum()) if count == 0: return 0, None, None # 5) Calc ranges vals = f_tensor[f_mask, ...] mins = np.min(vals, axis=0) maxs = np.max(vals, axis=0) ranges = {f.__name__: (float(mins[i]), float(maxs[i])) for i, f in enumerate(funcs_list)} # 6) Calc unavailable via separate method on the original tensor unavailable = calc_unavailable_params(params, param_filters, f_mask) return count, ranges, unavailable def print_result(filter_request, count, ranges, unavailable): print("---------") print(f"For applied filtering request {filter_request} \nSelected count:", count) if not count: print("No available configurations remaining.") return print("Ranges (min .. max):") for k, (lo, hi) in ranges.items(): print(f" {k:28s} {lo:8.2f} .. {hi:8.2f}") # --- Demo: show unavailable parameter values under current filters --- if unavailable: print("Unavailable values by parameter:") for pname, values in unavailable.items(): names = [v.name for v in values] print(f" {pname}: {names}") # -------- END Engine -------- # -- Usage Examples -- if __name__ == '__main__': # 1) Define product parameters params = [ ("fabric", [Fabrics.COTTON, Fabrics.LINEN, Fabrics.VELVET, Fabrics.WOOL, Fabrics.CHENILLE]), ("frame", [Frames.SMALL, Frames.MEDIUM, Frames.LARGE]), ("fill", [FillMaterials.FOAM, FillMaterials.DOWN]), ("wood_type", [WoodFinishes.OAK_LIGHT, WoodFinishes.OAK_DARK, WoodFinishes.WALNUT_LIGHT, WoodFinishes.WALNUT_DARK]), ("cushions", [Cushions.X1X1, Cushions.X1X2, Cushions.X2X2, Cushions.X3X3]), ] # -- Definition product Functions -- def price_regular(params_tuple, funcs_tuple, base_price=100): fabric, frame, fill, wood_finish, cushions = params_tuple raw = ( base_price + frame.fabric_surface * fabric.price_per_inch + frame.fill_volume_cubic_ft * fill.price_per_cubic_ft + frame.legs * wood_finish.species.price_per_leg ) return raw def price_effective(params_tuple, funcs_tuple, *, rounding_step: int = 20, minus_cent: float = 0.01): fabric, frame, fill, wood_finish, cushions = params_tuple price_regular = funcs_tuple[0] if fabric.discount_pct != 0.0: discounted = price_regular * ((100 - fabric.discount_pct) / 100) return math.ceil(discounted / rounding_step) * rounding_step - minus_cent else: return price_regular def availability_business_days(params_tuple, funcs_tuple, base_assembly_time: int = 4): fabric, frame, fill, wood_finish, cushions = params_tuple return base_assembly_time + max(fabric.availability, fill.availability, frame.assembly_bdays) def constraint_shade_matches(params_tuple, funcs_tuple): """Constraint: fabric shade must match the wood finish shade.""" fabric, frame, fill, wood_finish, cushions = params_tuple return fabric.shade is wood_finish.shade def constraint_fill_size_compat(params_tuple, funcs_tuple): """Constraint: Down fill requires Medium or Large frame due to weight/loft.""" fabric, frame, fill, wood_finish, cushions = params_tuple if fill is FillMaterials.DOWN: return frame.width >= 5 # Medium or Large return True funcs = [price_regular, price_effective, availability_business_days] # Constraint functions cfuncs = [constraint_shade_matches, constraint_fill_size_compat] # 3) Precalculate tensor f_tensor, c_tensor = prepare_tensor(params, funcs, dtype=np.float32, cfuncs=cfuncs) print("f_tensor:", f_tensor) # # f_tensor holds the evaluated function values (e.g. price, lead time) for every possible # combination of parameter values (the Cartesian product). Constraints (from cfuncs) are # computed into c_tensor, a boolean mask of which combinations are valid. # For example, constraints can reduce the total valid configurations far below the raw # product of all parameter choices. # 4) # --- Demo: parse the sample request, apply mask, and print ranges --- # # Request 1: Full Range, no filters. # -------------------------------------------------- # This request applies no parameter or property filters. # The full Cartesian product is 5 fabrics × 3 frames × 2 fills × 4 wood finishes × 4 cushions = 480 combinations. # However, constraints reduce this to 200: # - Shade constraint: Only wood-fabric pairs with matching shade are allowed (halves the combinations). # - Down fill is only available with Medium/Large frames (removes Down+Small combos). # The reported ranges show the minimum and maximum values for each function (price, etc.) across all valid configs. result = apply_request(params, funcs, f_tensor, {}, c_tensor=c_tensor) print_result({}, *result) # # Request 2: Medium frame + 2x2 cushions, with filters. # -------------------------------------------------- # Filters applied: # - Parameters: frame = Medium, cushions = 2x2 # - Properties: price_effective <= 200, availability_business_days <= 12 # This request restricts to Medium frames and 2x2 cushions, and only configurations # that are affordable and available within 12 days. # Result: count=3. Some fabrics (Velvet, Wool, Chenille) and fill (Down) are unavailable # due to either price, lead time, or constraints: # - availability <=12 removes Down (since Down is less available) # - shade constraint: if a fabric is light, only light woods are available, so some woods disappear # - price threshold: expensive fabrics are filtered out # The unavailable list shows which options are disabled because no valid config remains if you select them. filter_request = dict( params=[ ("frame", np.equal, Frames.MEDIUM), ("cushions", np.equal, Cushions.X2X2), ], props=[("price_effective", np.less_equal, 200), ("availability_business_days", np.less_equal, 12)], ) result = apply_request(params, funcs, f_tensor, filter_request, c_tensor=c_tensor) print_result(filter_request, *result) # # Request 3: Small frame + Velvet + Foam, price cap 250. # -------------------------------------------------- # Parameters: frame = Small, fabric = Velvet, fill = Foam # Property: price_effective <= 250 # Explanation: # - Velvet is a dark-shade fabric, so by the shade constraint, only dark wood finishes are allowed; # light woods are unavailable. # - Price cap of 250 does not remove any configs, since Small+Foam+Velvet is affordable. # - The count reflects 4 dark woods × 2x? cushions, etc.; the actual count depends on compatible options. # - The unavailable list will show, for example, that light woods are unavailable for this request, # but other parameters may remain fully available. filter_request = dict( params=[ ("frame", np.equal, Frames.SMALL), ("fabric", np.equal, Fabrics.VELVET), ("fill", np.equal, FillMaterials.FOAM), ], props=[("price_effective", np.less_equal, 250)], ) result = apply_request(params, funcs, f_tensor, filter_request, c_tensor=c_tensor) print_result(filter_request, *result)

The broader end-to-end request processing flow, including contextual adjustments, is shown in the diagram below.


Computation Layer

We deployed the computation layer on an Apache Helix cluster with Redis‑based messaging and lightweight client libraries for transparent scaling.

Performance outcome: four machines (4 CPUs each) sustained 8,000 requests per second — enabling real‑time configurator responsiveness across the client’s ecommerce ecosystem.

Business impact: this throughput translated into instantaneous configuration previews and faster product rollouts, reducing the time‑to‑market for new furniture lines by several weeks.


Implementation Highlights

  • Functional granularity: each attribute or price component acts as a reusable function.
  • Composability: multi‑component products (e.g., sectional sofas) are modeled as compositions of sub‑tensors.
  • Explainability: the system can identify which parameters cause a configuration to be invalid and recommend alternatives.
  • Timeline modeling: upcoming price or lead‑time changes are handled via hidden “timeline” parameters or pre‑cached tensor versions.

Co‑Innovation and Cloud Readiness

The Tensor‑Based CPQ solution was the outcome of close co‑innovation between the client’s merchandising experts and our engineering team at Grid Dynamics. Together, we combined domain expertise with advanced data‑modeling principles to design an architecture ready for cloud‑native scale.

The system follows the same distributed computation principles found in Google Cloud and AWS environments—offering future extensibility to GPU and WebAssembly‑based computation on the client side.


Conclusion

By representing configurable products as functions over parameter spaces, we redefined how enterprise retailers manage high-variability catalogs. Instead of scaling infrastructure to support millions of static SKUs, the system models configuration as a compact, dynamic tensor — enabling real-time navigation, explainability, and consistent price/lead-time computation across the entire product family.

For the client, this reduced time-to-market for new product lines from weeks to hours, improved pricing consistency across channels, and enabled seamless rollout of supply-chain-driven updates.

This work was a co-innovation effort between the client’s merchandising organization and Grid Dynamics engineering — demonstrating how mathematical modeling and system-level design can solve business problems that traditional CPQ platforms struggle to express.