/*
 * helmholtz_resonator_solvers.java
 */

import com.comsol.model.*;
import com.comsol.model.util.*;

/** Model exported on May 15 2026, 10:33 by COMSOL 6.4.0.421. */
public class helmholtz_resonator_solvers {

  public static Model run() {
    Model model = ModelUtil.create("Model");

//    From the File menu, choose New.
//    In the New window, click Model Wizard.
//    In the Model Wizard window, click 2D Axisymmetric.
//    In the Select Physics tree, select Acoustics > Pressure Acoustics > Pressure Acoustics, Frequency Domain (acpr).
//    Click Add.
//    Click Study.
//    In the Select Study tree, select General Studies > Frequency Domain.
//    Click Done.

    model.component().create("comp1", true);

    model.component("comp1").geom().create("geom1", 2);
    model.component("comp1").geom("geom1").axisymmetric(true);

    model.component("comp1").mesh().create("mesh1");

    model.component("comp1").physics().create("acpr", "PressureAcoustics", "geom1");

    model.study().create("std1");
    model.study("std1").create("freq", "Frequency");

//    Load all model parameters from a file; these include geometrical parameters and physical properties of the air.
//    In the Model Builder window, under Global Definitions, click Parameters 1.
//    In the Settings window for Parameters, locate the Parameters section.
//    Click Load from File.
//    Browse to the model's Application Library folder and double-click the file helmholtz_resonator_solvers_parameters.txt.
//    To import content from file, use:
//    model.param().loadFile("FILENAME");
    model.param().set("a", "0.01[m]", "Tube radius");
    model.param().set("L", "0.05[m]", "Tube length");
    model.param().set("Rv", "0.05[m]", "Volume radius");
    model.param().set("c_air", "343[m/s]", "Speed of sound");
    model.param().set("gamma", "0.82", "End-correction factor");
    model.param()
         .set("f_theo", "c_air/(2*pi)*sqrt(a^2/((L+gamma*a)*4/3*Rv^3))", "Lumped-parameter resonance frequency");
    model.param().set("fmax", "6000", "Maximum frequency for sweep");
    model.param().set("fmin", "0.1", "Minimum frequency for sweep");
    model.param().set("rho_air", "1.2[kg/m^3]", "Density of air");

//    In the Geometry toolbar, click Rectangle.

    model.component("comp1").geom("geom1").create("r1", "Rectangle");

//    You start by making the tube. It has length <c>L</c> and radius <c>a</c>.
//    In the Settings window for Rectangle, locate the Size and Shape section.
//    In the Width text field, type a.

    model.component("comp1").geom("geom1").feature("r1").set("size", new String[]{"a", "1"});

//    In the Height text field, type L.

    model.component("comp1").geom("geom1").feature("r1").set("size", new String[]{"a", "L"});

//    Locate the Position section.
//    In the z text field, type -L.

    model.component("comp1").geom("geom1").feature("r1").set("pos", new String[]{"0", "-L"});

//    In the Geometry toolbar, click Circle.

    model.component("comp1").geom("geom1").run("r1");
    model.component("comp1").geom("geom1").create("c1", "Circle");

//    Proceed to make the volume which has radius <c>Rv</c> and is placed after the tube. You ensure an overlap of <c>a/2</c> knowing that you will merge the tube and volume into one object (the resonator). Note that objects or parts of objects extending into the left-hand side of the symmetry line (<eqv>r</eqv> = 0) will be removed, so you will not have to manually remove the left half of the circle.
//    In the Settings window for Circle, locate the Size and Shape section.
//    In the Radius text field, type Rv.

    model.component("comp1").geom("geom1").feature("c1").set("r", "Rv");

//    Locate the Position section.
//    In the z text field, type -(L+Rv-a/2).

    model.component("comp1").geom("geom1").feature("c1").set("pos", new String[]{"0", "-(L+Rv-a/2)"});

//    In the Geometry toolbar, click Booleans and Partitions and choose Union.

    model.component("comp1").geom("geom1").run("c1");
    model.component("comp1").geom("geom1").create("uni1", "Union");

//    Clean up the remaining parts of the geometry.
//    Click in the Graphics window and then press Ctrl+A to select both objects.

    model.component("comp1").geom("geom1").feature("uni1").selection("input").set("c1", "r1");

//    In the Model Builder window, right-click Geometry 1 and choose Delete Entities.

    model.component("comp1").geom("geom1").run("uni1");
    model.component("comp1").geom("geom1").create("del1", "Delete");

//    On the object uni1, select Boundaries 1, 2, 5.

    model.component("comp1").geom("geom1").feature("del1").selection("input").set("uni1", 1, 2, 5);

//    In the Settings window for Delete Entities, click Build All Objects.

    model.component("comp1").geom("geom1").runPre("fin");

//    Click the Zoom Extents button in the Graphics toolbar.
//    In the Geometry toolbar, click Build All.

    model.component("comp1").geom("geom1").run("fin");

//    Now add the material. For this simple model it suffices to use tabulated values for the physical parameters of air, which can be defined manually by adding a blank material and assign to it the values <c>rho_air</c> and <c>c_air</c>. Alternatively load air from the Material Library.
//    In the Model Builder window, right-click Component 1 (comp1) > Materials and choose Blank Material.

    model.component("comp1").material().create("mat1", "Common");

//    In the Settings window for Material, type My air in the Label text field.

    model.component("comp1").material("mat1").label("My air");

//    Locate the Material Contents section.
//    In the table, enter the following settings:

    model.component("comp1").material("mat1").propertyGroup("def").set("density", new String[]{"rho_air"});
    model.component("comp1").material("mat1").propertyGroup("def").set("soundspeed", new String[]{"c_air"});

//    Next define an integration coupling that integrates over the volume <c>V</c> which will be used for evaluating the system response.
//    In the Definitions toolbar, click Nonlocal Couplings and choose Integration.

    model.component("comp1").cpl().create("intop1", "Integration");
    model.component("comp1").cpl("intop1").set("axisym", true);

//    Select Domain 1.

    model.component("comp1").cpl("intop1").selection().set(1);

//    Next, you define that you will be solving Pressure Acoustics in the entire domain, and you apply the boundary conditions.
//    Click the Zoom Extents button in the Graphics toolbar.
//    In the Physics toolbar, click Boundaries and choose Pressure.

    model.component("comp1").physics("acpr").create("pr1", "Pressure", 1);

//    Select Boundary 3.

    model.component("comp1").physics("acpr").feature("pr1").selection().set(3);

//    The applied pressure is a combination of an constant part and a <c>linper</c> operator. The constant part will be used in the Frequency Domain steps and inactive in the rest, while the <c>linper</c> part will be active when running the Frequency Domain, Modal Step and inactive in the rest, as described in Notes About the COMSOL Implementation.
//    In the Settings window for Pressure, locate the Pressure section.
//    In the \[p_0\] text field, type 1+linper(1).

    model.component("comp1").physics("acpr").feature("pr1").set("p0", "1+linper(1)");

//    Set up the AWE expression used for the adaptive frequency sweep solver.
//    In the Physics toolbar, click Global and choose AWE Expression.

    model.component("comp1").physics("acpr").create("awee1", "AsymptoticWaveformEvaluationExpression", -1);

//    In the Settings window for AWE Expression, locate the Asymptotic Waveform Evaluation Expression section.
//    In the \[L_\textrm{AWE}\] text field, type intop1(abs(p)^2)/1[Pa^2]/intop1(1).

    model.component("comp1").physics("acpr").feature("awee1").set("AWE", "intop1(abs(p)^2)/1[Pa^2]/intop1(1)");

//    Mesh
//    Proceed and generate the mesh using the <l>Physics-controlled mesh</l> functionality. The frequency controlling the maximum element size is entered manually to accommodate the different solvers to be used. In general, 5 to 6 second-order elements per wavelength are needed to resolve the waves. For more details, see <em>Meshing (Resolving the Waves)</em> in the <em>Acoustics Module User's Guide</em>. The default <l>Automatic</l> option gives 5 elements per wavelength. In this case, use a mesh size 8 times finer to artificially increase the run time and demonstrate differences in the running time with the different solvers.
//    In the Model Builder window, under Component 1 (comp1), click Mesh 1.
//    In the Settings window for Mesh, locate the Pressure Acoustics, Frequency Domain (acpr) section.
//    From the Maximum mesh element size control parameter list, select Frequency.

    model.component("comp1").physics("acpr").prop("MeshControl").set("SizeControlParameter", "Frequency");

//    In the \[f_\textrm{max}\] text field, type fmax.

    model.component("comp1").physics("acpr").prop("MeshControl")
         .set("PhysicsControlledMeshMaximumFrequency", "fmax");

//    From the Number of mesh elements per wavelength list, select User defined.

    model.component("comp1").physics("acpr").prop("MeshControl").set("ElementsPerWavelength", "UserDefined");

//    In the text field, type 40.

    model.component("comp1").physics("acpr").prop("MeshControl").set("nperlambda", 40);

//    Click Build All.

    model.component("comp1").mesh("mesh1").run();

//    Next, set up the first of the three solvers (Frequency Domain solver). You already included this solver (this study) when you started this analysis, so you can immediately set it up.
//    In the Model Builder window, click Study 1.
//    In the Settings window for Study, type Study 1 - Frequency sweep in the Label text field.

    model.study("std1").label("Study 1 - Frequency sweep");

//    Locate the Study Settings section.
//    Clear the Generate default plots checkbox.

    model.study("std1").setGenPlots(false);

//    In the Model Builder window, under Study 1 - Frequency sweep, click Step 1: Frequency Domain.
//    In the Settings window for Frequency Domain, locate the Study Settings section.
//    In the Frequencies text field, type 10^{range(log10(fmin),(log10(fmax)-(log10(fmin)))/499,log10(fmax))}.

    model.study("std1").feature("freq")
         .set("plist", "10^{range(log10(fmin),(log10(fmax)-(log10(fmin)))/499,log10(fmax))}");

//    This command selects 500 frequencies in the range <c>fmin</c> - <c>fmax</c> which will be evenly spaced when shown on a logarithmic axis.
//    In the Study toolbar, click Compute.

    model.study("std1").createAutoSequences("all");

    model.sol("sol1").runAll();

//    Now use the <l>Adaptive Frequency Sweep</l> study which uses the asymptotic waveform evaluation (AWE) method. You perform this as a separate study to be able to compare the two solutions.
//    In the Home toolbar, click Add Study to open the Add Study window.
//    Find the Studies subsection.
//    In the Select Study tree, select Preset Studies for Selected Physics Interfaces > Adaptive Frequency Sweep.
//    Click Add Study in the window toolbar.

    model.study().create("std2");
    model.study("std2").create("frawe", "FrequencyAdaptive");
    model.study("std2").feature("frawe").set("awefunctype", "usercontrolled");
    model.study("std2").feature("frawe").set("solnum", "auto");
    model.study("std2").feature("frawe").set("notsolnum", "auto");
    model.study("std2").feature("frawe").set("outputmap", new String[]{});

//    In the Home toolbar, click Add Study to close the Add Study window.
//    In the Settings window for Adaptive Frequency Sweep, locate the Study Settings section.
//    In the Frequencies text field, type 10^{range(log10(fmin),(log10(fmax)-(log10(fmin)))/499,log10(fmax))}.

    model.study("std2").feature("frawe")
         .set("plist", "10^{range(log10(fmin),(log10(fmax)-(log10(fmin)))/499,log10(fmax))}");

//    From the AWE expression type list, select Physics controlled.

    model.study("std2").feature("frawe").set("awefunctype", "physicscontrolled");

//    In the Relative tolerance text field, type 0.005.

    model.study("std2").feature("frawe").set("rtol", 0.005);

//    The tolerance of the AWE solver is reduced to ensure the accuracy of the results.
//    In the Model Builder window, click Study 2.
//    In the Settings window for Study, locate the Study Settings section.
//    Clear the Generate default plots checkbox.

    model.study("std2").setGenPlots(false);

//    In the Label text field, type Study 2 - Frequency sweep with AWE.

    model.study("std2").label("Study 2 - Frequency sweep with AWE");

//    In the Study toolbar, click Compute.

    model.study("std2").createAutoSequences("all");

    model.sol("sol2").runAll();

//    In the Home toolbar, click Add Study to open the Add Study window.
//    Find the Studies subsection.
//    In the Select Study tree, select Preset Studies for Selected Physics Interfaces > Frequency Domain, Modal.
//    Click Add Study in the window toolbar.

    model.study().create("std3");
    model.study("std3").create("eig", "Eigenfrequency");
    model.study("std3").feature("eig").set("ftplistmethod", "manual");
    model.study("std3").feature("eig").set("neigs", "10");
    model.study("std3").feature("eig").set("shift", "1[Hz]");
    model.study("std3").feature("eig").set("eigwhich", "lr");
    model.study("std3").feature("eig").set("chkeigregion", true);
    model.study("std3").feature("eig").set("storefact", false);
    model.study("std3").feature("eig").set("linpsolnum", "auto");
    model.study("std3").feature("eig").set("outputmap", new String[]{});
    model.study("std3").feature("eig").set("ngenAUX", "1");
    model.study("std3").feature("eig").set("goalngenAUX", "1");
    model.study("std3").feature("eig").set("ngenAUX", "1");
    model.study("std3").feature("eig").set("goalngenAUX", "1");
    model.study("std3").create("frmod", "Frequencymodal");
    model.study("std3").feature("frmod").set("ftplistmethod", "manual");
    model.study("std3").feature("frmod").set("linpsolnum", "auto");
    model.study("std3").feature("frmod").set("solnum", "auto");
    model.study("std3").feature("frmod").set("notsolnum", "auto");
    model.study("std3").feature("frmod").set("outputmap", new String[]{});

//    In the Home toolbar, click Add Study to close the Add Study window.
//    You then set up the third solver; the Frequency Domain Modal study.
//    In the Settings window for Study, locate the Study Settings section.
//    Clear the Generate default plots checkbox.

    model.study("std3").setGenPlots(false);

//    In the Label text field, type Study 3 - Modal solver frequency sweep.

    model.study("std3").label("Study 3 - Modal solver frequency sweep");

//    In the Model Builder window, under Study 3 - Modal solver frequency sweep, click Step 1: Eigenfrequency.
//    In the Settings window for Eigenfrequency, locate the Study Settings section.
//    From the Eigenfrequency search method list, select Rectangle.

    model.study("std3").feature("eig").set("eigmethod", "region");

//    Next, change the upper limit of the real part of the frequencies you want to investigate to make sure the entire frequency range <c>fmin</c> - <c>fmax</c> is appropriately resolved.
//    Find the Rectangle search region subsection.
//    In the Largest real part (Eigenfrequency) text field, type 1.5*fmax.

    model.study("std3").feature("eig").set("eiglr", "1.5*fmax");

//    In the Model Builder window, click Step 2: Frequency Domain, Modal.
//    In the Settings window for Frequency Domain, Modal, locate the Study Settings section.
//    In the Frequencies text field, type 10^{range(log10(fmin),(log10(fmax)-(log10(fmin)))/499,log10(fmax))}.

    model.study("std3").feature("frmod")
         .set("plist", "10^{range(log10(fmin),(log10(fmax)-(log10(fmin)))/499,log10(fmax))}");

//    In the Study toolbar, click Compute.

    model.study("std3").createAutoSequences("all");

    model.sol("sol3").runAll();

//    In the Results toolbar, click 1D Plot Group.

    model.result().create("pg1", "PlotGroup1D");
    model.result("pg1").run();

//    In the Settings window for 1D Plot Group, type Frequency Sweeps - Logarithmic Scale in the Label text field.

    model.result("pg1").label("Frequency Sweeps - Logarithmic Scale");

//    Click to expand the Title section.
//    From the Title type list, select Label.

    model.result("pg1").set("titletype", "label");

//    Locate the Plot Settings section.
//    Select the x-axis label checkbox.

    model.result("pg1").set("xlabelactive", true);

//    In the associated text field, type Frequency (Hz).

    model.result("pg1").set("xlabel", "Frequency (Hz)");

//    Select the y-axis label checkbox.

    model.result("pg1").set("ylabelactive", true);

//    In the associated text field, type Average |p| (Pa).

    model.result("pg1").set("ylabel", "Average |p| (Pa)");

//    Locate the Axis section.
//    Select the x-axis log scale checkbox.

    model.result("pg1").set("xlog", true);

//    Select the y-axis log scale checkbox.

    model.result("pg1").set("ylog", true);

//    Locate the Legend section.
//    From the Position list, select Upper left.

    model.result("pg1").set("legendpos", "upperleft");

//    In the Frequency Sweeps - Logarithmic Scale toolbar, click More Plots and choose Octave Band.

    model.result("pg1").create("oct1", "OctaveBand");
    model.result("pg1").feature("oct1").set("quantity", "bandpower");
    model.result("pg1").feature("oct1").set("markerpos", "datapoints");
    model.result("pg1").feature("oct1").set("linewidth", "preference");

//    In the Settings window for Octave Band, locate the Data section.
//    From the Dataset list, select Study 1 - Frequency sweep/Solution 1 (sol1).

    model.result("pg1").feature("oct1").set("data", "dset1");

//    Locate the Selection section.
//    From the Geometric entity level list, select Domain.

    model.result("pg1").feature("oct1").selection().geom("geom1", 2);

//    Select Domain 1.

    model.result("pg1").feature("oct1").selection().set(1);

//    Locate the y-Axis Data section.
//    In the Expression text field, type abs(acpr.p_t).

    model.result("pg1").feature("oct1").set("expr", "abs(acpr.p_t)");

//    From the Expression type list, select General (non-dB).

    model.result("pg1").feature("oct1").set("exprtype", "general");

//    Locate the Plot section.
//    From the Quantity list, select Continuous power spectral density.

    model.result("pg1").feature("oct1").set("quantity", "continuous");

//    Click to expand the Legends section.
//    Select the Show legends checkbox.

    model.result("pg1").feature("oct1").set("legend", true);

//    From the Legends list, select Manual.

    model.result("pg1").feature("oct1").set("legendmethod", "manual");

//    In the table, enter the following settings:

    model.result("pg1").feature("oct1").setIndex("legends", "Frequency sweep", 0);

//    Right-click Octave Band 1 and choose Duplicate.

    model.result("pg1").feature().duplicate("oct2", "oct1");
    model.result("pg1").run();

//    In the Settings window for Octave Band, locate the Data section.
//    From the Dataset list, select Study 2 - Frequency sweep with AWE/Solution 2 (sol2).

    model.result("pg1").feature("oct2").set("data", "dset2");

//    Click to expand the Coloring and Style section.
//    Find the Line style subsection.
//    From the Line list, select None.

    model.result("pg1").feature("oct2").set("linestyle", "none");

//    Find the Line markers subsection.
//    From the Marker list, select Plus sign.

    model.result("pg1").feature("oct2").set("linemarker", "plus");

//    From the Positioning list, select Interpolated.

    model.result("pg1").feature("oct2").set("markerpos", "interp");

//    In the Number text field, type 50.

    model.result("pg1").feature("oct2").set("markers", 50);

//    Locate the Legends section.
//    In the table, enter the following settings:

    model.result("pg1").feature("oct2").setIndex("legends", "Frequency sweep with AWE", 0);

//    Right-click Octave Band 2 and choose Duplicate.

    model.result("pg1").feature().duplicate("oct3", "oct2");
    model.result("pg1").run();

//    In the Settings window for Octave Band, locate the Data section.
//    From the Dataset list, select Study 3 - Modal solver frequency sweep/Solution 3 (sol3).

    model.result("pg1").feature("oct3").set("data", "dset3");

//    Locate the Coloring and Style section.
//    Find the Line markers subsection.
//    From the Marker list, select Point.

    model.result("pg1").feature("oct3").set("linemarker", "point");

//    Locate the Legends section.
//    In the table, enter the following settings:

    model.result("pg1").feature("oct3").setIndex("legends", "Modal solver frequency sweep", 0);

//    In the Frequency Sweeps - Logarithmic Scale toolbar, click Line Segments.

    model.result("pg1").create("lnsg1", "LineSegments");
    model.result("pg1").feature("lnsg1").set("markerpos", "datapoints");
    model.result("pg1").feature("lnsg1").set("linewidth", "preference");

//    In the Settings window for Line Segments, locate the Data section.
//    From the Dataset list, select Study 1 - Frequency sweep/Solution 1 (sol1).

    model.result("pg1").feature("lnsg1").set("data", "dset1");

//    From the Parameter selection (freq) list, select First.

    model.result("pg1").feature("lnsg1").setIndex("looplevelinput", "first", 0);

//    Locate the x-Coordinates section.
//    In the table, enter the following settings:

    model.result("pg1").feature("lnsg1").setIndex("xexpr", "f_theo", 0);
    model.result("pg1").feature("lnsg1").setIndex("xexpr", "f_theo", 1);
    model.result("pg1").feature("lnsg1").setIndex("yexpr", 0.1, 0);

//    Locate the y-Coordinates section.
//    In the table, enter the following settings:

    model.result("pg1").feature("lnsg1").setIndex("yexpr", 100, 1);

//    Click to expand the Coloring and Style section.
//    From the Color list, select Black.

    model.result("pg1").feature("lnsg1").set("linecolor", "black");

//    Click to expand the Legends section.
//    Select the Show legends checkbox.

    model.result("pg1").feature("lnsg1").set("legend", true);

//    From the Legends list, select Manual.

    model.result("pg1").feature("lnsg1").set("legendmethod", "manual");

//    In the table, enter the following settings:

    model.result("pg1").feature("lnsg1").setIndex("legends", "Theoretical resonance", 0);

//    In the Frequency Sweeps - Logarithmic Scale toolbar, click Plot.

    model.result("pg1").run();
    model.result("pg1").run();

//    In the Model Builder window, right-click Frequency Sweeps - Logarithmic Scale and choose Duplicate.

    model.result().duplicate("pg2", "pg1");
    model.result("pg2").run();

//    In the Settings window for 1D Plot Group, locate the Axis section.
//    Clear the x-axis log scale checkbox.

    model.result("pg2").set("xlog", false);

//    In the Label text field, type Frequency Sweeps - Linear Scale.

    model.result("pg2").label("Frequency Sweeps - Linear Scale");

//    Locate the Legend section.
//    From the Position list, select Upper right.

    model.result("pg2").set("legendpos", "upperright");
    model.result().dataset().create("rev1", "Revolve2D");
    model.result().dataset("rev1").set("data", "dset1");
    model.result().dataset("rev1").set("revangle", 225);
    model.result().dataset("rev1").set("startangle", -90);
    model.result().dataset("rev1").set("defaultPlotIDs", new String[]{"pg3|acpr", "pg4|acpr"});
    model.result().create("pg3", "PlotGroup3D");
    model.result("pg3").set("data", "rev1");
    model.result("pg3").setIndex("looplevel", 500, 0);
    model.result("pg3").create("surf1", "Surface");
    model.result("pg3").feature("surf1").set("expr", new String[]{"acpr.Lp_t"});
    model.result("pg3").feature("surf1").set("colortable", "Rainbow");
    model.result("pg3").feature("surf1").set("colorscalemode", "linear");
    model.result("pg3").set("showlegendsunit", true);
    model.result("pg3").label("Sound Pressure Level, 3D (acpr)");
    model.result("pg3").label("Sound Pressure Level, 3D (acpr)");
    model.result("pg3").run();
    model.result("pg3").setIndex("looplevel", 424, 0);
    model.result("pg3").set("showlegendsmaxmin", true);
    model.result("pg3").run();

    model.title("Helmholtz Resonator Analyzed with Different Frequency Domain Solvers");

    model
         .description("This example simulates a frequency sweep of a generic Helmholtz resonator \u2014 a classic acoustics resonating circuit with a known theoretical solution \u2014 to illustrate how to use different solvers in the frequency domain. In addition to the stationary solver, the example also uses the Asymptotic Waveform Evaluation solver and the stationary Frequency Domain-Modal solvers, which both reconstruct the result based on expansions around a few exact solutions in the sweep range.");

    return model;
  }

  public static void main(String[] args) {
    run();
  }

}
