예제 #1
0
def test_where_casadi():
    a = cas.GenDM_ones(4)
    b = 2 * cas.GenDM_ones(4)

    c = np.where(cas.DM([1, 0, 1, 0]), a, b)

    assert np.all(c == cas.DM([1, 2, 1, 2]))
예제 #2
0
def fit(
        model,  # type: callable
        x_data,  # type: dict
        y_data,  # type: np.ndarray
        param_guesses,  # type: dict
        param_bounds=None,  # type: dict
        weights=None,  # type: np.ndarray
        verbose=True,  # type: bool
        scale_problem=True,  # type: bool
        put_residuals_in_logspace=False,  # type: bool
):
    """
    Fits a model to data through least-squares minimization.
    :param model: A callable with syntax f(x, p) where:
            x is a dict of dependent variables. Same format as x_data [dict of 1D ndarrays of length n].
            p is a dict of parameters. Same format as param_guesses [dict of scalars].
        Model should use CasADi functions for differentiability.
    :param x_data: a dict of dependent variables. Same format as model's x. [dict of 1D ndarrays of length n]
    :param y_data: independent variable. [1D ndarray of length n]
    :param param_guesses: a dict of fit parameters. Same format as model's p. Keys are parameter names, values are initial guesses. [dict of scalars]
    :param param_bounds: Optional: a dict of bounds on fit parameters.
        Keys are parameter names, values are a tuple of (min, max).
        May contain only a subset of param_guesses if desired.
        Use None to represent one-sided constraints (i.e. (None, 5)).
        [dict of tuples]
    :param weights: Optional: weights for data points. If not supplied, weights are assumed to be uniform.
        Weights are automatically normalized. [1D ndarray of length n]
    :param verbose: Whether or not to print information about parameters and goodness of fit.
    :param scale_problem: Whether or not to attempt to scale variables, constraints, and objective for more robust solve. [boolean]
    :param put_residuals_in_logspace: Whether to optimize using the logarithmic error as opposed to the absolute error (useful for minimizing percent error).
        Note: If any model outputs or data are negative, this will fail!
    :return: Optimal fit parameters [dict]
    """
    opti = cas.Opti()

    # Handle weighting
    if weights is None:
        weights = cas.GenDM_ones(y_data.shape[0])
    weights /= cas.sum1(weights)

    def fit_param(initial_guess, lower_bound=None, upper_bound=None):
        """
        Helper function to create a fit variable
        :param initial_guess:
        :param lower_bound:
        :param upper_bound:
        :return:
        """
        if scale_problem and np.abs(initial_guess) > 1e-8:
            var = initial_guess * opti.variable()  # scale variables
        else:
            var = opti.variable()
        opti.set_initial(var, initial_guess)
        if lower_bound is not None:
            lower_bound_abs = np.abs(lower_bound)
            if scale_problem and lower_bound_abs > 1e-8:
                opti.subject_to(
                    var / lower_bound_abs > lower_bound / lower_bound_abs)
            else:
                opti.subject_to(var > lower_bound)
        if upper_bound is not None:
            upper_bound_abs = np.abs(upper_bound)
            if scale_problem and upper_bound_abs > 1e-8:
                opti.subject_to(
                    var / upper_bound_abs < upper_bound / upper_bound_abs)
            else:
                opti.subject_to(var < upper_bound)
        return var

    if param_bounds is None:
        params = {k: fit_param(param_guesses[k]) for k in param_guesses}
    else:
        params = {
            k: fit_param(param_guesses[k]) if k not in param_bounds else
            fit_param(param_guesses[k], param_bounds[k][0], param_bounds[k][1])
            for k in param_guesses
        }

    if scale_problem:
        y_model_initial = model(x_data, param_guesses)
        if not put_residuals_in_logspace:
            residuals_initial = y_model_initial - y_data
        else:
            residuals_initial = cas.log10(y_model_initial) - cas.log10(y_data)
        SSE_initial = cas.sum1(weights * residuals_initial**2)

        y_model = model(x_data, params)
        if not put_residuals_in_logspace:
            residuals = y_model - y_data
        else:
            residuals = cas.log10(y_model) - cas.log10(y_data)
        SSE = cas.sum1(weights * residuals**2)
        opti.minimize(SSE / SSE_initial)

    else:
        y_model = model(x_data, params)
        if not put_residuals_in_logspace:
            residuals = y_model - y_data
        else:
            residuals = cas.log10(y_model) - cas.log10(y_data)
        SSE = cas.sum1(weights * residuals**2)
        opti.minimize(SSE)

    # Solve
    p_opts = {}
    s_opts = {}
    s_opts["max_iter"] = 3e3  # If you need to interrupt, just use ctrl+c
    # s_opts["mu_strategy"] = "adaptive"
    opti.solver('ipopt', p_opts, s_opts)
    opti.solver('ipopt')
    if verbose:
        sol = opti.solve()
    else:
        with stdout_redirected():
            sol = opti.solve()

    params_solved = {}
    for k in params:
        try:
            params_solved[k] = sol.value(params[k])
        except:
            params_solved[k] = np.NaN

    # printing
    if verbose:
        # Print parameters
        print("\nFit Parameters:")
        if len(params_solved) <= 20:
            [print("\t%s = %f" % (k, v)) for k, v in params_solved.items()]
        else:
            print("\t%i parameters solved for." % len(params_solved))
        print("\nGoodness of Fit:")

        # Print RMS error
        weighted_RMS_error = sol.value(
            cas.sqrt(cas.sum1(weights * residuals**2)))
        print("\tWeighted RMS error: %f" % weighted_RMS_error)

        # Print R^2
        y_data_mean = cas.sum1(y_data) / y_data.shape[0]
        SS_tot = cas.sum1(weights * (y_data - y_data_mean)**2)
        SS_res = cas.sum1(weights * (y_data - y_model)**2)
        R_squared = sol.value(1 - SS_res / SS_tot)
        print("\tR^2: %f" % R_squared)

    return params_solved
nominal_diameter = cas.exp(log_nominal_diameter)

thickness = 0.14e-3 * 5
opti.subject_to([
    nominal_diameter > thickness,
])

# Bending loads

I = cas.pi / 64 * ((nominal_diameter + thickness)**4 -
                   (nominal_diameter - thickness)**4)
EI = E * I
total_lift_force = 9.81 * 103.873 / 2
lift_distribution = "elliptical"
if lift_distribution == "rectangular":
    force_per_unit_length = total_lift_force * cas.GenDM_ones(n) / L
elif lift_distribution == "elliptical":
    force_per_unit_length = total_lift_force * cas.sqrt(1 - (x / L)**2) * (
        4 / cas.pi) / L

# Torsion loads
J = cas.pi / 32 * ((nominal_diameter + thickness)**4 -
                   (nominal_diameter - thickness)**4)

airfoil_lift_coefficient = 1
airfoil_moment_coefficient = -0.14
airfoil_chord = 1  # meter
moment_per_unit_length = force_per_unit_length * airfoil_moment_coefficient * airfoil_chord / airfoil_lift_coefficient
# Derivation of above:
#   CL = L / q c
#   CM = M / q c**2
log_nominal_diameter = opti.variable(n)
opti.set_initial(log_nominal_diameter, cas.log(200e-3))
nominal_diameter = cas.exp(log_nominal_diameter)

thickness = 0.14e-3 * 5
opti.subject_to([
    nominal_diameter > thickness,
])
I = cas.pi / 64 * ((nominal_diameter + thickness)**4 -
                   (nominal_diameter - thickness)**4)
EI = E * I
total_lift_force = 9.81 * 103.873 / 2
lift_distribution = "elliptical"
if lift_distribution == "rectangular":
    q = total_lift_force * cas.GenDM_ones(n) / L
elif lift_distribution == "elliptical":
    q = total_lift_force * cas.sqrt(1 - (x / L)**2) * (4 / cas.pi) / L

u = 1 * opti.variable(n)
du = 0.1 * opti.variable(n)
ddu = 0.01 * opti.variable(n)
dEIddu = 100 * opti.variable(n)
# opti.set_initial(u, 2 * (x/L)**4)
# opti.set_initial(du, 2 * 4/L * (x/L)**3)
# opti.set_initial(ddu, 2 * 3/L * 2/L * (x/L))
# opti.set_initial(dEIddu, 2 * 3/L * 2/L * 1/L * 1e3)

# Add forcing term
ddEIddu = q