Skip to main content

Quickstart (10 minutes)

Welcome! In this Quickstart, we’ll guide you through building your first model in Dalus. We’re going to build a model of a satellite with 3 reaction wheels, design a simple attitude control system, and then verify a key performance parameter, settling_time, for that control system. Whether you’re new to system modeling or just getting started with Dalus, this hands-on example will help you get up to speed quickly and confidently.

Step 1: Create a New Model

Start by creating a new model in the Dalus dashboard. Give your model a descriptive name, such as “Satellite Quickstart”. This model is the isolated, collaborative environment that will contain your system’s requirements, architecture, and analysis all in one place. Creating a new model in Dalus

Step 2: Define Requirements

Satellites need to respond to new attitude commands from the ground promptly to face the necessary direction for imaging, telemetry, etc.. Let’s add a requirement that specifies the maximum allowable time to complete a commanded attitude maneuver:
Requirement: The Attitude Control and Determination Subsystem (ACDS) shall settle to within 0.1 degrees of the commanded attitude within 120 seconds after maneuver initiation.
Adding requirements in Dalus

Step 3: Build the Structure

Now let’s define the basic structure of our satellite. We’ll start by adding the main parts we’ll be focusing on for this quickstart:
  1. Add the Satellite as the root part of your model.
  2. Double-Click into the Satellite and add these subparts:
    • Reaction Wheels (x3): Add three reaction wheel components to represent the 3-axis control system.
    • Attitude Control & Determination System (ACDS): Add the Attitude Control and Determination Subsystem as a separate part.

Step 4: Add Part Attributes

Now that we have the foundation, let’s assign some attributes. Attributes are values like “mass” or “moment of inertia” that describe a part’s physical properties and are accessible by any analysis we do. Add the satellite’s mass-moments of inertia as attributes in the Right Sidebar:
Attribute NameValueUnit
l_xx10kilogram meter squared
l_yy10kilogram meter squared
l_zz10kilogram meter squared
Adding attributes in Dalus

Step 5: Configure an Action (Analysis)

In this step, we’ll implement the action Point Satellite inside the Satellite part, which takes a commanded attitude and calculates how long it takes (among other things) to reach that attitude from its initial attitude (0,0,0).
  1. First, switch to the Action view in the Dalus interface. This will allow you to create and configure actions for your model, in this case, for the Satellite part.
  2. Add an action and name it “Point Satellite”.
  3. Double-click on the action and copy and paste this python code inside, which implements a simple Proportional-Derivative controller in 3 axes:
import numpy as np


def main():
    # Satellite inertia (from attributes)
    I_xx = float(getAttribute("89a03e89-ce41-4e50-a3ed-76822d253cf9"))
    I_yy = float(getAttribute("60a9955b-54c2-4229-a46d-67251e7f0a85"))
    I_zz = float(getAttribute("7d66485b-a0b5-472c-b932-06c90be29357"))
    I = np.array([I_xx, I_yy, I_zz])

    # Control gains and limits
    Kp = np.array([0.1, 0.1, 0.1])
    Kd = np.array([1.5, 1.5, 1.5])
    torque_cmd_max = 0.02

    # Reaction wheel parameters
    J_rw = np.array([1.0, 1.0, 1.0])  # kg·m²
    efficiency = 0.9

    # Initial attitude (deg) and desired attitude (deg)
    a_x, a_y, a_z = 0.0, 0.0, 0.0
    a_cmd_x, a_cmd_y, a_cmd_z = getInput("a_cmd_x"), getInput("a_cmd_y"), getInput("a_cmd_z")

    attitude = np.deg2rad([a_x, a_y, a_z])
    desired_attitude = np.deg2rad([a_cmd_x, a_cmd_y, a_cmd_z])

    # Simulation settings
    dt = 0.1
    settling_threshold_deg = 0.1

    # Histories (use lists so the run can extend indefinitely)
    times = []
    error_norm_hist = []
    torque_hist = []
    power_hist = []

    omega = np.zeros(3)
    omega_rw = np.zeros(3)

    prev_error = desired_attitude - attitude
    initial_error_norm = np.linalg.norm(prev_error)
    settling_time = None

    t = 0.0
    while True:
        error = desired_attitude - attitude
        error_dot = (error - prev_error) / dt
        prev_error = error

        # PD control with saturation
        torque_cmd = Kp * error + Kd * error_dot
        torque_cmd = np.clip(torque_cmd, -torque_cmd_max, torque_cmd_max)

        # Update satellite dynamics
        alpha = torque_cmd / I
        omega += alpha * dt
        attitude += omega * dt

        # Reaction wheel dynamics & power
        alpha_rw = torque_cmd / J_rw
        omega_rw += alpha_rw * dt
        power = np.abs(torque_cmd * omega_rw) / efficiency

        # Record histories
        times.append(t)
        error_norm_val = np.linalg.norm(error)
        error_norm_hist.append(error_norm_val)
        torque_hist.append(torque_cmd)
        power_hist.append(power)

        # Settling check
        if np.rad2deg(error_norm_val) < settling_threshold_deg:
            settling_time = t
            break

        t += dt

    # Convert histories to arrays for analysis
    times = np.asarray(times)
    error_norm_hist = np.asarray(error_norm_hist)
    torque_hist = np.asarray(torque_hist)
    power_hist = np.asarray(power_hist)

    # Rise time (10% to 90% of initial error)
    e0 = initial_error_norm
    rise_time = np.nan
    idx_start = np.where(error_norm_hist <= 0.9 * e0)[0]
    idx_end = np.where(error_norm_hist <= 0.1 * e0)[0]
    if len(idx_start) > 0 and len(idx_end) > 0:
        t_rise_start = times[idx_start[0]]
        t_rise_end = times[idx_end[0]]
        if t_rise_end >= t_rise_start:
            rise_time = t_rise_end - t_rise_start

    # Total energy consumed by wheels
    total_power = np.sum(power_hist, axis=1)
    total_energy = np.trapz(total_power, times)  # Joules

    # Results
    if np.isnan(rise_time):
        print("Rise time: N/A (threshold not met during run)")
    else:
        print(f"Rise time: {rise_time:.1f} s")

    print(f"Settling time: {settling_time:.1f} s")

    setAttribute("73fa8e84-1faa-4fd1-bb9b-5ae59d7dbd25", settling_time)

    roll_deg, pitch_deg, yaw_deg = np.rad2deg(attitude)
    print(f"Final attitude: roll={roll_deg:.2f} deg, pitch={pitch_deg:.2f} deg, yaw={yaw_deg:.2f} deg")


if __name__ == "__main__":
    main()
You’ll notice in the script, on line 22, we’re grabbing some input variables: a_cmd_x, a_cmd_y, and a_cmd_z. These need to be added to the action via the Right Sidebar. These are the commanded attitudes we are providing the script:
Input VariableValueUnit
a_cmd_x50deg
a_cmd_y50deg
a_cmd_z50deg
The controller will also use the I_xx, I_yy, and I_zz attributes you defined earlier. Copy their IDs from the Attributes dropdown and paste them in the getAttribute function’s argument. Finally, we need to set the settling_time; this is the key performance parameter we are evaluating. Go back to the Satellite part and add it as an attribute.
Attribute NameValueUnit
settling_time0s
Go back to the Point Satellite script and copy the attribute ID and paste it in setAttribute("", settling_time) between the "".
  • After running the simulation, the script will calculate the settling_time and set it for the Satellite’s attribute.

Step 6: Add a Requirement Constraint and Test

Now, let’s return to our requirements and add a constraint to the ACDS settling time requirement:
  1. Go to the requirements view and locate the ACDS settling time requirement.
  2. Click on its status, then under Add constraint, select the Attributes dropdown.
  3. Choose settling_time and set the limit to < 120 seconds.
  4. You’ll see the requirement status immediately update based on its current value.
Experiment a bit. Increase the moments of inertia (e.g., I_xx) in the Satellite’s attributes and re-run the simulation. Try to get the requirement to fail!

Step 7: Create a Simple State Machine

Next, let’s create a simple state machine to trigger our Point Satellite action instead of manually running it.
  1. Switch to the State view.
  2. Define two states: STANDBY and SURVEILLING.
  3. Connect them to add a transition between them. Name it anything.
  4. In the Point Satellite transition, select the Point Satellite action as an effect. This causes the action to execute whenever you transition from Standby to Surveilling.
  5. Use the states dropdown to first enter STANDBY. Then, switch to the Action view and transition to SURVEILLING to observe the effect taking place.
Although simple, this powerful principle can be used to orchestrate many actions for a complex state machine. Additional state machine features coming soon include:
  1. transition triggers - boolean expressions that automatically execute the transition if ALL evaluate to true.
  2. transition guards - boolean expressions that prevent the transition if ANY evaluate to true.

Next Steps

Ready to take your model to the next level? Here are a few ways you can extend and improve your satellite model:
  1. Parameterize Control Gains & Reaction Wheel Moments:
    • Add the proportional (Kp) and derivative (Kd) gains as attributes to the ACDS subsystem instead of hard-coding them in the Point Satellite script.
    • Similarly, add the Reaction Wheel moments (J_rw_x, J_rw_y, J_rw_z) as attributes of the Reaction Wheels .
    • Update the Point Satellite script to fetch these values using getAttribute. This is to organize your parameters logically, making them easy to find and tune as your model grows in complexity.
  2. Model Command Inputs from Ground Control:
    • Add a new part called Ground Control at the same level as the Satellite.
    • Connect the parts together to form an interface and add connections for a_cmd_x, a_cmd_y, and a_cmd_z instead of assigning them as inputs to the action.
    • In your script, use getConnection (passing in the ID) to access these command variables instead of getInput.
  3. Set and Reset the Satellite’s Current Attitude:
    • Add attitude_x, attitude_y, and attitude_z attributes (0 deg) to the Satellite in the Right Sidebar → Attributes.
    • Update the Point Satellite script to fetch these values and use them as our initial attitude instead of hard-coding the initial attitude to (0,0,0) (see line 22).
    • At the bottom of the Point Satellite script, use setAttribute(id, value) to write back the final attitude_x, attitude_y, and attitude_z (degrees).
    • In the actions view inside Satellite, create an action named “Reset Attitude” that sets the three attitude attributes back to 0.
    • In the states view inside Satellite, add a Reset Satellite transition from SURVEILLING to STANDBY, and set STANDBY’s Entry Action to execute Reset Attitude.
    • Now, transition the Satellite back and forth between SURVEILLING and STANDBY to see its attitude change.
These improvements will make your model more realistic and modular. See if you can implement them—and experiment with different values and configurations to see how your system responds!