Introduction to Multi‑disciplinary Optimization and Monorepo Challenges

Multi‑disciplinary optimization (MDO) is a cornerstone of modern engineering. Whether designing an aircraft wing, a wind turbine, or a complex software system with front‑end, back‑end, and machine‑learning components, the discipline requires simultaneous consideration of multiple, often conflicting, objectives. In a typical MDO workflow, specialists from aerodynamics, structures, controls, thermal analysis, and other domains must share data, iterate on designs, and converge to a globally optimal solution. The complexity grows exponentially as the number of disciplines increases.

Managing the code, models, and tools for such projects is notoriously difficult. Each discipline may use different languages (Python for simulation, C++ for high‑performance solvers, JavaScript/TypeScript for user interfaces), different build systems, and different version‑control strategies. The result is often a fragmented landscape of separate repositories, manual data transfer, broken dependencies, and wasted effort on duplicate builds. This is where Nx – a powerful monorepo toolchain – can transform the way engineering teams approach MDO.

Nx, originally built on top of the Angular CLI, has evolved into a general‑purpose monorepo toolkit that supports a wide range of frameworks and languages. Its ability to provide centralized project management, intelligent task orchestration, incremental builds, and cross‑discipline dependency tracking makes it an ideal platform for MDO projects. In this article, we explore how to leverage Nx for multi‑disciplinary optimization, covering its core concepts, benefits, implementation strategies, and real‑world use cases.

What Is Nx?

Nx is a build system and monorepo management tool that helps you develop, test, and build multiple projects within a single repository. It extends the capabilities of the Angular CLI but now works seamlessly with React, Node.js, Next.js, NestJS, Vue, and many other frameworks and libraries. Nx provides:

  • Project Graph: A dependency graph that shows exactly how your projects relate to each other. Nx understands which projects depend on which, and it can determine the minimal set of affected projects for any change.
  • Task Orchestrator: Run tasks (build, test, lint, serve) across your projects in parallel, in order, or with custom scheduling. Nx automatically caches task results, so if nothing has changed, the task is effectively instant.
  • Smart Rebuilds and Retesting: The affected command runs tasks only on projects that have changed since a given base commit, dramatically speeding up CI pipelines.
  • Generators and Executors: Scaffold new projects, libraries, and components with consistent structure. Executors allow you to run custom commands (e.g., a Python simulation or a solver invocation) as first‑class Nx tasks.
  • Distributed Caching with Nx Cloud: Share task caches across your team and CI agents, avoiding redundant work.

For MDO projects, the key features are the dependency graph, the caching mechanism, and the ability to mix multiple languages and build tools within a single workspace. Nx treats every discipline as a project or library, respecting its unique requirements while providing a unified interface for orchestration.

Key Benefits of Using Nx for MDO Projects

Centralized Management and Dependency Tracking

In a traditional MDO setup, each discipline might maintain its own repository, script suite, and data files. Synchronizing changes becomes a manual, error‑prone process. With Nx, all disciplines live in one monorepo. The project graph gives you a live map of interdependencies: an aerodynamic analysis library may depend on a shared geometry library; a structural optimization tool may depend on both. When the geometry library changes, Nx instantly knows which other modules need to be rebuilt or retested.

Enhanced Collaboration Across Teams

Engineers from different backgrounds can work in a shared environment without stepping on each other’s toes. Tag‑based constraints allow you to define boundaries – for example, “a structures project cannot depend on an aerodynamics library directly unless through a public interface.” Nx’s lint rules enforce these boundaries, preventing accidental coupling. Code reviews become simpler because the monorepo provides a single source of truth, and the affected commands help reviewers focus only on the changed portions.

Scalability for Large‑scale Optimization

MDO projects often involve hundreds of modules, thousands of files, and complex simulation chains. Nx is built to handle monorepos with tens of thousands of projects. Its caching mechanism works per‑task and per‑file, so even if you have many disciplines, you rarely run the same computation twice. Parallel task execution (using nx run-many --parallel) fully utilizes multi‑core machines and CI clusters.

Built‑in Tooling for Quality and Automation

Nx ships with integrated testing (Jest, Cypress, Playwright), linting (ESLint), and formatting (Prettier). For MDO, these tools can be applied not only to code but also to configuration files, simulation inputs, and even validation scripts. You can, for instance, create an Nx executor that runs a regression test on an aerodynamic solver’s output. This ensures that optimizations don’t introduce regressions.

Computation Caching – A Game Changer for Iterative Optimization

MDO is inherently iterative. An optimization algorithm may request dozens or hundreds of design evaluations. With Nx’s caching, if a discipline’s code or input hasn’t changed, its previous output is reused without rerunning the solver. This is especially powerful when different optimization cycles share common intermediate results. The cache can be local or distributed via Nx Cloud, so even parallel optimization runs across multiple CI agents can share results.

Cross‑Language and Cross‑Tool Integration

Nx is language‑agnostic at the task level. You can define an executor that spawns a Python script for computational fluid dynamics, a C++ executable for finite element analysis, and a Node.js service for data assimilation. All these tasks are managed by Nx’s task graph, respecting dependencies and caching. This makes Nx a unifying layer over heterogeneous MDO toolsets.

Implementing Nx in Your MDO Workflow

Transitioning a multi‑disciplinary project into an Nx monorepo involves several steps. Below is a practical guide, illustrated with an aerospace example.

Step 1: Set Up the Nx Workspace

Create a new Nx workspace using the command:

npx create-nx-workspace@latest aerospace-mdo --preset=empty

The aerospace-mdo workspace will be the home for all disciplines. Choose the package manager of your preference (npm, yarn, pnpm) and commit the generated structure to version control.

Step 2: Structure Disciplines as Projects or Libraries

Each major discipline should become an Nx ”project.” For example, create an application for the overall optimization wrapper or pipeline, and libraries for individual analyzers:

  • aerodynamics – a library containing the fluid dynamics solver configuration and wrapper.
  • structures – a library for the structural analysis solver.
  • optimizer – an application that orchestrates the optimization loop.
  • shared-geometry – a library with common geometry definitions and conversion utilities.

Use nx g @nx/js:lib aerodynamics or nx g @nx/node:lib structures to generate consistent structures. Nx generators enforce best practices and ensure that each project has its own project.json for task configuration.

Step 3: Define Project Boundaries and Tags

Use tags to enforce discipline‑coupling rules. Edit the nx.json or individual project.json files to add tags like type:discipline, scope:external, etc. For example, you can create an ESLint rule that forbids a scope:structures library from importing from scope:aerodynamics directly – only through scope:shared. This keeps dependencies clean and prevents circular dependencies.

Step 4: Integrate Optimization Tools as Executors

Nx executors allow you to wrap any command as a task. For the aerodynamics solver, create an executor that runs a Python script. For the structural solver, perhaps a C++ executable. Example executor configuration in project.json for the aerodynamics library:

{
  "targets": {
    "solve": {
      "executor": "nx:run-commands",
      "options": {
        "command": "python solvers/aero/main.py --input={projectRoot}/input.json --output={projectRoot}/output.json",
        "cwd": "{workspaceRoot}"
      }
    }
  }
}

Now you can run nx solve aerodynamics, and Nx will handle caching and dependency ordering automatically. Use outputs to specify which files are cached (e.g., "outputs": ["{projectRoot}/output.json"]).

Step 5: Automate the Optimization Loop

The optimizer application can define a target that runs the entire MDO cycle. For example, a run-optimization target that executes the optimizer script, which in turn calls Nx tasks for each discipline via Node.js child processes or by using the Nx programmatic API. Because every discipline solver is an Nx task, the optimizer can leverage Nx’s runMany and cached results to accelerate iteration.

Step 6: Set Up CI with Affected Commands

In your CI pipeline (GitHub Actions, GitLab CI, etc.), use nx affected:lint, nx affected:test, and nx affected:build to run checks only on changed projects. For MDO, you may also want an nx affected:solve target that reruns only the solvers for changed disciplines. This dramatically reduces feedback time.

Case Study 1: Aerodynamic and Structural Optimization of an Aircraft Wing

Consider a multidisciplinary team working on a new aircraft wing. The project involves three primary disciplines: aerodynamics (to predict lift and drag), structures (to ensure strength and weight constraints are met), and an optimization algorithm that adjusts wing shape parameters. Without Nx, engineers would maintain separate repositories for each solver, manually transfer geometry files, and run the optimization loop with ad‑hoc scripts. With Nx, the setup is unified.

The workspace contains:

  • wing-geometry – a library that defines the wing shape (airfoil coordinates, twist distribution, etc.). It outputs a JSON file used by both solvers.
  • aero-solver – a library with an executor that runs a CFD code (e.g., OpenFOAM or SU2). It depends on wing-geometry.
  • struct-solver – a library with an executor that runs a finite element solver (e.g., CalculiX or Abaqus). It also depends on wing-geometry.
  • mdo-orchestrator – an application that runs the optimization loop (e.g., using a surrogate model or direct search). It depends on both solvers.

When an engineer updates the wing‑geometry library, Nx marks both solvers as affected. The next optimization iteration (via nx run mdo-orchestrator:optimize) automatically rebuilds or re‑caches the solvers. The iterative optimization becomes fast because Nx caches solver outputs for given geometry inputs. If the geometry reverts to an earlier version, the cache is reused without recomputation. This results in a 60–80% reduction in total optimization time compared to a non‑cached workflow.

Case Study 2: Automotive Thermal and Structural Optimization

In automotive engineering, an EV battery pack must be optimized for thermal management and structural crashworthiness simultaneously. The disciplines are thermal simulation (CFD/heat transfer) and structural simulation (FEA). They share a common CAD model of the battery pack. Nx is used to create a monorepo that includes:

  • battery-cad – a library that manages the parametric CAD model (exported as STEP or mesh).
  • thermal-solver – a library using an executor for a thermal solver (e.g., Star‑CCM+ or a custom Python script).
  • crash-solver – a library with an executor for an explicit dynamics solver (e.g., LS‑DYNA).
  • optimizer – an application that runs a multi‑objective genetic algorithm.

The monorepo approach allows the CAD engineer to make a change and see immediately which solvers are affected. With Nx’s distributed caching, a CI pipeline running on 32 parallel agents can evaluate multiple designs simultaneously, sharing cached thermal results across agents. The project graph reveals that the crash solver does not depend on the thermal solver output directly (only on the shared CAD), so changes to thermal model parameters do not invalidate crash simulation caches – a critical feature for efficiency.

Advanced Techniques for Nx‑Powered MDO

Custom Executors for Non‑JavaScript Tools

While Nx is built on Node.js, its executor system can invoke any command. For solvers written in Python, Fortran, or CUDA, create a simple executor that runs the external binary and captures stdout/stderr. Use the nx:run-commands executor or create a custom one with the Nx Executor API. This allows you to enforce caching and dependency tracking for tools that otherwise have no notion of incremental builds.

Using Nx’s Computation Caching with Remote Agents

Nx Cloud enables distributed caching across your team and CI agents. In an MDO context, this means that if a design point has already been simulated by any team member or any CI job, the result is immediately available. This is particularly valuable when exploring the design space with optimization algorithms like genetic algorithms or particle swarm – many design points are evaluated in parallel, and caching prevents redundant solver runs.

Incremental Code Generation for MDO Templates

Nx generators can be used to scaffold discipline‑specific code templates. For example, create a custom generator that produces a new aerodynamics solver project with the correct directory structure, executor configuration, and test stubs. This ensures consistency and reduces setup time when adding a new discipline to the optimization.

Integrating with Data Management Tools

Many MDO projects rely on a data warehouse or a design repository (e.g., Directus). With Nx, you can create a library that acts as a client to your data API. The library can be shared across all disciplines, ensuring a single source of truth for design variables, constraints, and metadata. Nx’s dependency graph will show which projects use this library, and changes to the data schema will automatically trigger rebuilds of consuming projects.

Overcoming Common Pitfalls

Avoiding Monolithic Dependencies

One risk of monorepos is that disciplines become too tightly coupled. Use Nx’s tags and ESLint import restrictions to enforce a clean architecture. For example, allow only shared libraries to be imported by multiple disciplines; each discipline should depend on shared types and interfaces, not on another discipline’s implementation.

Handling Large Binary Files

Solvers often produce large output files (grids, solution fields, etc.). Nx caches based on file hashes, so storing large outputs can bloat the cache. Solution: mark the solver’s output as a single summary file (e.g., result.json with key performance indicators) and cache that instead. Keep full solver outputs outside the Nx cache (e.g., in a shared storage or a separate archive).

Ensuring Reproducibility

MDO results must be reproducible. With Nx, the entire workspace is versioned, and task caching includes inputs (source files, configurations, even environment variables if declared). This makes it easier to roll back to a specific design iteration and rerun the optimization identically. Use lockfiles (package-lock.json, yarn.lock